Search code examples
c#.netlinqenumerator

Get next N elements from enumerable


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

private static IEnumerable<int> RandomNumberGenerator() {
    while (true) yield return GenerateRandomNumber(0, 100);
}

I need to group those numbers in groups of 10, so I would like something like:

foreach (IEnumerable<int> group in RandomNumberGenerator().Slice(10)) {
    Assert.That(group.Count() == 10);
}

I have defined Slice method, but I feel there should be one already defined. Here is my Slice method, just for reference:

    private static IEnumerable<T[]> Slice<T>(IEnumerable<T> enumerable, int size) {
        var result = new List<T>(size);
        foreach (var item in enumerable) {
            result.Add(item);
            if (result.Count == size) {
                yield return result.ToArray();
                result.Clear();
            }
        }
    }

Question: is there an easier way to accomplish what I'm trying to do? Perhaps Linq?

Note: above example is a simplification, in my program I have an Iterator that scans given matrix in a non-linear fashion.

EDIT: Why Skip+Take is no good.

Effectively what I want is:

var group1 = RandomNumberGenerator().Skip(0).Take(10);
var group2 = RandomNumberGenerator().Skip(10).Take(10);
var group3 = RandomNumberGenerator().Skip(20).Take(10);
var group4 = RandomNumberGenerator().Skip(30).Take(10);

without the overhead of regenerating number (10+20+30+40) times. I need a solution that will generate exactly 40 numbers and break those in 4 groups by 10.


Solution

  • 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());
    }