Edit:你的方法没有任何问题,除了当你枚举它时,抛出可枚举确实会“繁荣”。这就是它的目的。它没有一个合适的GetEnumerator
对其进行定义。所以你的代码没有表现出真正的问题。在第一种情况下,通过这样做First
,你只是枚举直到第一个结果集(只是{ 1, 2, 3 }
)被获得并且没有枚举抛出可枚举(这意味着Concat
没有被执行)。但在第二个示例中,您要求的元素位于2
分割后,这意味着它也会枚举投掷可枚举,并且会“繁荣”。这里的关键是要理解ElementAt
枚举集合直到索引要求并且本质上不是懒惰的(它不可能是)。
我不确定完全懒惰是否是正确的选择。问题在于,惰性分割为外部序列和内部序列的整个过程在一个枚举器上运行,这可能会根据枚举器状态产生不同的结果。例如,您仅枚举外部序列,内部序列将不再是您所期望的。或者,如果只枚举一半的外部序列和一个内部序列,那么其他内部序列的状态会是什么?你的方法是最好的。
下面的方法是懒惰的(仍然会繁荣,因为这是有保证的),因为它不使用中间具体实现,但可能比原来的方法慢,因为它多次遍历列表:
public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source,
Func<T, bool> separatorPredicate,
bool includeEmptyEntries = false,
bool includeSeparators = false)
{
int prevIndex = 0;
int lastIndex = 0;
var query = source.Select((t, index) => { lastIndex = index; return new { t, index }; })
.Where(a => separatorPredicate(a.t));
foreach (var item in query)
{
if (item.index == prevIndex && !includeEmptyEntries)
{
prevIndex++;
continue;
}
yield return source.Skip(prevIndex)
.Take(item.index - prevIndex + (!includeSeparators ? 0 : 1));
prevIndex = item.index + 1;
}
if (prevIndex <= lastIndex)
yield return source.Skip(prevIndex);
}
总的来说,你原来的方法是最好的。如果您需要完全懒惰的东西,那么我的以下答案很合适。请注意,它仅适用于以下情况:
foreach (var inners in outer)
foreach (var item in inners)
{
}
and not
var outer = sequence.Split;
var inner1 = outer.First;
var inner2 = outer.ElementAt; //etc
换句话说,不适合同一内部序列的多次迭代。如果您完全意识到这种危险的构造:
原答案:
这不使用中间具体集合,不ToList
在源可枚举上,并且完全是惰性/迭代器式的:
public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source,
Func<T, bool> separatorPredicate,
bool includeEmptyEntries = false,
bool includeSeparator = false)
{
using (var x = source.GetEnumerator())
while (x.MoveNext())
if (!separatorPredicate(x.Current))
yield return x.YieldTill(separatorPredicate, includeSeparator);
else if (includeEmptyEntries)
{
if (includeSeparator)
yield return Enumerable.Repeat(x.Current, 1);
else
yield return Enumerable.Empty<T>();
}
}
static IEnumerable<T> YieldTill<T>(this IEnumerator<T> x,
Func<T, bool> separatorPredicate,
bool includeSeparator)
{
yield return x.Current;
while (x.MoveNext())
if (!separatorPredicate(x.Current))
yield return x.Current;
else
{
if (includeSeparator)
yield return x.Current;
yield break;
}
}
简短、甜蜜、简单。我添加了一个附加标志来表示是否要返回空集(默认情况下它会忽略)。如果没有这个标志,代码会更加简洁。
感谢您提出这个问题,这将在我的扩展方法库中! :)