缓存LINQ查询-IEnumerable.Skip()



考虑以下代码

IEnumerable<Items> remainingItems = Items
var results = new List<List<Items>>();
var counter = 0;
while (remainingItems.Any())
{
var result = new List<Item>();
result.AddRange(remainingItems.TakeWhile(x => somePredicate(x, counter));
results.Add(result);
remainingItems = remainingItems.Skip(result.Count);
counter++;
}

如果不清楚发生了什么,我取一个Ienumerable,迭代它,直到一个谓词失败,把所有这些项放在一堆里,然后继续迭代其余的项,直到下一个谓词失效,把所有的项放在一起。冲洗,清洗,重复。

现在我想重点讨论的是Ienumerable.Skip((

由于它使用延迟执行,这意味着我必须遍历每个循环中已经跳过的所有元素。

我可以使用ToList((强制它进行求值,但它需要遍历所有剩余的项才能进行求值,这同样糟糕。

所以我真正需要的是一个IEnumerable,它可以急切地跳过,并存储我们到达的最后一个点,然后继续。所以我需要一些功能,比如:

IEnumerable.SkipAndCache(n),它允许我访问从第n个项目开始的IE分子。

有什么想法吗?

您可以使用MoreLinq。有一个称为Memoize的实验函数,它延迟缓存序列。所以代码看起来是这样的:

while (remainingItems.Any())
{
var result = new List<Item>();
result.AddRange(remainingItems.TakeWhile(x => somePredicate(x, counter));
results.Add(result);
remainingItems = remainingItems.Skip(result.Count).Memoize();
counter++;
}

这里的结果不会具体化,因为它仍然是懒惰的评估:

remainingItems = remainingItems.Skip(result.Count).Memoize();

在这里,remainingItems序列将被评估和缓存(迭代器不会像ToList中那样遍历所有元素(:

remainingItems.Any()

这里将使用缓存:

result.AddRange(remainingItems.TakeWhile(x => somePredicate(x, counter));

要使用此方法,您需要添加:

using MoreLinq.Experimental;

由于我们正在跳过系列中的结果集,为什么不使用for循环来进行类似的操作呢

for(int i = 0 ; i < result.Count ; i++){
//do some business logic and now i got X result
i = i + X
}

如果我正确理解你的问题,收益率可能会很有用

public static IEnumerable<IEnumerable<T>> Test<T>(IEnumerable<T> source)
{
var items = new List<T>();
foreach (T item in source)
{
items.Add(item);
if (!SomePredicate(item))
{
yield return items;
items = new List<T>();
}
}
// if you want any remaining items to go into their own IEnumerable, even if there's no more fails
if (items.Count > 0)
{
yield return items;
}
}

就像这个例子一样,我将失败条件设置为!item % 10 == 0,并将值0到1000传递给上面的方法。我得到101个第一个包含0的IEnumerables,其余包含1到10、11到20等。

您可以编写一个简单的扩展方法来帮助实现这一点:

public static IEnumerable<IEnumerable<T>> PartitionBy<T>(this IEnumerable<T> sequence, Func<T, int, bool> predicate)
{
var block = new List<T>();
int index = 0;
foreach (var item in sequence)
{
if (predicate(item, index++))
{
block.Add(item);
}
else if (block.Count > 0)
{
yield return block.ToList(); // Return a copy so the caller can't change our local list.
block.Clear();
}
}
if (block.Count > 0)
yield return block; // No need for a copy since we've finished using our local list.
}

(作为一种扩展方法,您需要将其放在一个静态类中。(

然后你可以用它来对数据进行这样的分区

static void Main()
{                         //  0  1  2  3  4  5  6  7  8  9
var ints = new List<int> {0, 1, 0, 3, 4, 5, 0, 0, 8, 9};
var result = ints.PartitionBy(((item, index) => item == index)); // Items where value == index.
foreach (var seq in result)
Console.WriteLine(string.Join(", ", seq));
// Output is:
// 0, 1
// 3, 4, 5
// 8, 9
}

请注意,此实现会跳过与谓词不匹配的元素。


这里有一个不复制数据的更复杂的替代实现:

class Indexer
{
public int  Index;
public bool Finished;
}
public static IEnumerable<IEnumerable<T>> PartitionBy<T>(this IEnumerable<T> sequence, Func<T, int, bool> predicate)
{
var iter = sequence.GetEnumerator();
var indexer = new Indexer();
while (!indexer.Finished)
{
yield return nextBlock(iter, predicate, indexer);
}
}
static IEnumerable<T> nextBlock<T>(IEnumerator<T> iter, Func<T, int, bool> predicate, Indexer indexer)
{
int index = indexer.Index;
bool any = false;
while (true)
{
if (!iter.MoveNext())
{
indexer.Finished = true;
yield break;
}
if (predicate(iter.Current, index++))
{
any = true;
yield return iter.Current;
}
else
{
indexer.Index = index;
if (any)
yield break;
}
}
}

最新更新