Get next N elements from enumerable

后端 未结 10 1492
遥遥无期
遥遥无期 2020-12-16 17:38

Context: C# 3.0, .Net 3.5
Suppose I have a method that generates random numbers (forever):

private static IEnumerable RandomNumberGenerator(         


        
相关标签:
10条回答
  • 2020-12-16 18:02

    Let's see if you even need the complexity of Slice. If your random number generates is stateless, I would assume each call to it would generate unique random numbers, so perhaps this would be sufficient:

    var group1 = RandomNumberGenerator().Take(10);  
    var group2 = RandomNumberGenerator().Take(10);  
    var group3 = RandomNumberGenerator().Take(10);  
    var group4 = RandomNumberGenerator().Take(10);
    

    Each call to Take returns a new group of 10 numbers.

    Now, if your random number generator re-seeds itself with a specific value each time it's iterated, this won't work. You'll simply get the same 10 values for each group. So instead, you would use:

    var generator  = RandomNumberGenerator();
    var group1     = generator.Take(10);  
    var group2     = generator.Take(10);  
    var group3     = generator.Take(10);  
    var group4     = generator.Take(10);
    

    This maintains an instance of the generator so that you can continue retrieving values without re-seeding the generator.

    0 讨论(0)
  • 2020-12-16 18:03

    It seems like we'd prefer for an IEnumerable<T> to have a fixed position counter so that we can do

    var group1 = items.Take(10);
    var group2 = items.Take(10);
    var group3 = items.Take(10);
    var group4 = items.Take(10);
    

    and get successive slices rather than getting the first 10 items each time. We can do that with a new implementation of IEnumerable<T> which keeps one instance of its Enumerator and returns it on every call of GetEnumerator:

    public class StickyEnumerable<T> : IEnumerable<T>, IDisposable
    {
        private IEnumerator<T> innerEnumerator;
    
        public StickyEnumerable( IEnumerable<T> items )
        {
            innerEnumerator = items.GetEnumerator();
        }
    
        public IEnumerator<T> GetEnumerator()
        {
            return innerEnumerator;
        }
    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return innerEnumerator;
        }
    
        public void Dispose()
        {
            if (innerEnumerator != null)
            {
                innerEnumerator.Dispose();
            }
        }
    }
    

    Given that class, we could implement Slice with

    public static IEnumerable<IEnumerable<T>> Slices<T>(this IEnumerable<T> items, int size)
    {
        using (StickyEnumerable<T> sticky = new StickyEnumerable<T>(items))
        {
            IEnumerable<T> slice;
            do
            {
                slice = sticky.Take(size).ToList();
                yield return slice;
            } while (slice.Count() == size);
        }
        yield break;
    }
    

    That works in this case, but StickyEnumerable<T> is generally a dangerous class to have around if the consuming code isn't expecting it. For example,

    using (var sticky = new StickyEnumerable<int>(Enumerable.Range(1, 10)))
    {
        var first = sticky.Take(2);
        var second = sticky.Take(2);
        foreach (int i in second)
        {
            Console.WriteLine(i);
        }
        foreach (int i in first)
        {
            Console.WriteLine(i);
        }
    }
    

    prints

    1
    2
    3
    4
    

    rather than

    3
    4
    1
    2
    
    0 讨论(0)
  • 2020-12-16 18:04

    You could use the Skip and Take methods with any Enumerable object.

    For your edit :

    How about a function that takes a slice number and a slice size as a parameter?

    private static IEnumerable<T> Slice<T>(IEnumerable<T> enumerable, int sliceSize, int sliceNumber) {
        return enumerable.Skip(sliceSize * sliceNumber).Take(sliceSize);
    }
    
    0 讨论(0)
  • 2020-12-16 18:04

    Take a look at Take(), TakeWhile() and Skip()

    0 讨论(0)
  • 2020-12-16 18:05

    I have done something similar. But I would like it to be simpler:

    //Remove "this" if you don't want it to be a extension method
    public static IEnumerable<IList<T>> Chunks<T>(this IEnumerable<T> xs, int size)
    {
        var curr = new List<T>(size);
    
        foreach (var x in xs)
        {
            curr.Add(x);
    
            if (curr.Count == size)
            {
                yield return curr;
                curr = new List<T>(size);
            }
        }
    }
    

    I think yours are flawed. You return the same array for all your chunks/slices so only the last chunk/slice you take would have the correct data.

    Addition: Array version:

    public static IEnumerable<T[]> Chunks<T>(this IEnumerable<T> xs, int size)
    {
        var curr = new T[size];
    
        int i = 0;
    
        foreach (var x in xs)
        {
            curr[i % size] = x;
    
            if (++i % size == 0)
            {
                yield return curr;
                curr = new T[size];
            }
        }
    }
    

    Addition: Linq version (not C# 2.0). As pointed out, it will not work on infinite sequences and will be a great deal slower than the alternatives:

    public static IEnumerable<T[]> Chunks<T>(this IEnumerable<T> xs, int size)
    {
        return xs.Select((x, i) => new { x, i })
                 .GroupBy(xi => xi.i / size, xi => xi.x)
                 .Select(g => g.ToArray());
    }
    
    0 讨论(0)
  • 2020-12-16 18:06

    I got this solution for the same problem:

    int[] ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    IEnumerable<IEnumerable<int>> chunks = Chunk(ints, 2, t => t.Dump());
    //won't enumerate, so won't do anything unless you force it:
    chunks.ToList();
    
    IEnumerable<T> Chunk<T, R>(IEnumerable<R> src, int n, Func<IEnumerable<R>, T> action){
      IEnumerable<R> head;
      IEnumerable<R> tail = src;
      while (tail.Any())
      {
        head = tail.Take(n);
        tail = tail.Skip(n);
        yield return action(head);
      }
    }
    

    if you just want the chunks returned, not do anything with them, use chunks = Chunk(ints, 2, t => t). What I would really like is to have to have t=>t as default action, but I haven't found out how to do that yet.

    0 讨论(0)
提交回复
热议问题