从枚举中获取下一个N个元素

THX*_*138 11 .net c# linq enumerator

上下文:C#3.0,.Net 3.5
假设我有一个生成随机数的方法(永远):

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

我需要将这些数字分组为10组,所以我想要像:

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

我已经定义了Slice方法,但我觉得应该已经定义了一个.这是我的Slice方法,仅供参考:

    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();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

问题:有没有更简单的方法来完成我想要做的事情?也许是Linq?

注意:上面的例子是一个简化,在我的程序中我有一个Iterator,它以非线性的方式扫描给定的矩阵.

编辑:为什么Skip+ Take不好.

实际上我想要的是:

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);
Run Code Online (Sandbox Code Playgroud)

没有再生数(10 + 20 + 30 + 40)次的开销.我需要一个能产生40个数字的解决方案,然后将4个组中的数字分成10个.

Mic*_*ael 12

跳过,并采取任何使用你的?

在循环中使用两者的组合来获得您想要的.

所以,

list.Skip(10).Take(10);
Run Code Online (Sandbox Code Playgroud)

跳过前10条记录然后接下来的10条记录.


Las*_*olt 7

我做了类似的事情.但我希望它更简单:

//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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为你的是有缺陷的.为所有块/切片返回相同的数组,因此只有最后一个块/切片才能获得正确的数据.

增加:数组版本:

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];
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

增加: Linq版本(不是C#2.0).正如所指出的那样,它不会对无限序列起作用,并且会比替代方案慢很多:

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());
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*Tao 6

使用SkipTake将是一个非常糟糕的主意.调用Skip索引集合可能没问题,但是任意调用它都会IEnumerable<T>导致枚举超过跳过的元素数量,这意味着如果你反复调用它,你会在序列上枚举一个数量级更多时间比你需要的多.

抱怨所有你想要的"过早优化"; 但那太荒谬了.

我认为你的Slice方法和它一样好.我打算提出一种不同的方法来提供延迟执行并避免中间数组分配,但这是一个危险的游戏(即,如果你尝试类似于ToList这样的结果IEnumerable<T>实现,而不是枚举内部集合,你最终会陷入无尽的循环中.

(我已经删除了原来的内容,因为自从发布问题后OP的改进已经使我的建议变得多余了.)