代码之家  ›  专栏  ›  技术社区  ›  Yair Halberstadt

缓存LINQ查询-IEnumerable.Skip()

  •  4
  • Yair Halberstadt  · 技术社区  · 6 年前

    考虑以下代码

    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个项开始的IEnumerator。

    有什么想法吗?

    4 回复  |  直到 6 年前
        1
  •  1
  •   arekzyla    6 年前

    你可以用 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;
    
        2
  •  0
  •   Devesh    6 年前

    既然我们在序列中跳过了结果集,为什么不使用for循环

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

    如果我对你的问题理解正确的话,让步也许是有用的

    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个IEnumerables,第一个包含0,其余包含1到10,11到20,等等。

        4
  •  0
  •   Matthew Watson    6 年前

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

    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.
    }
    

    (作为扩展方法,需要将其放入静态类中。)

    然后你可以用它来划分数据。在本例中,我们将把一个int列表分成几个分区,其中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;
            }
        }
    }