如何将IEnumerable <String>拆分为IEnumerable <string>组

Kev*_*ter 30 c# linq

我有一个IEnumerable<string>我想分成三组,所以如果我的输入有6个项目,我会得到一个IEnumerable<IEnumerable<string>>返回的两个项目,每个项目将包含IEnumerable<string>我的字符串内容.

我正在寻找如何使用Linq而不是简单的for循环

谢谢

Meh*_*ari 30

var result = sequence.Select((s, i) => new { Value = s, Index = i })
                     .GroupBy(item => item.Index / 3, item => item.Value);
Run Code Online (Sandbox Code Playgroud)

请注意,这将返回一个IEnumerable<IGrouping<int,string>>功能类似于您想要的功能.但是,如果您严格需要将其键入IEnumerable<IEnumerable<string>>(要传递给C#3.0中不支持泛型差异的方法),您应该使用Enumerable.Cast:

var result = sequence.Select((s, i) => new { Value = s, Index = i })
                     .GroupBy(item => item.Index / 3, item => item.Value)
                     .Cast<IEnumerable<string>>();
Run Code Online (Sandbox Code Playgroud)

  • 在获得任何结果之前,GroupBy是否必须迭代整个序列,或者您是否仍然在此处执行延迟执行? (2认同)
  • 对@Don,GroupBy的评价并不像其他Linq方法那样懒惰.它在返回任何组之前枚举所有序列. (2认同)

Mat*_*son 29

这是对此线程的迟回复,但这是一个不使用任何临时存储的方法:

public static class EnumerableExt
{
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> input, int blockSize)
    {
        var enumerator = input.GetEnumerator();

        while (enumerator.MoveNext())
        {
            yield return nextPartition(enumerator, blockSize);
        }
    }

    private static IEnumerable<T> nextPartition<T>(IEnumerator<T> enumerator, int blockSize)
    {
        do
        {
            yield return enumerator.Current;
        }
        while (--blockSize > 0 && enumerator.MoveNext());
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一些测试代码:

class Program
{
    static void Main(string[] args)
    {
        var someNumbers = Enumerable.Range(0, 10000);

        foreach (var block in someNumbers.Partition(100))
        {
            Console.WriteLine("\nStart of block.");

            foreach (int number in block)
            {
                Console.Write(number);
                Console.Write(" ");
            }
        }

        Console.WriteLine("\nDone.");
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 注意:了解此代码不是线程安全的至关重要!在将结果传递给异步代码之前,您必须同步将生成的ienumebles转换为具体类型,以确保批量正确收集项目. (3认同)

dic*_*d30 21

我知道这已经得到了回答,但是如果你打算经常采用IEnumerables的片段,那么我建议像这样制作一个通用的扩展方法:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkSize)
{
    return source.Where((x,i) => i % chunkSize == 0).Select((x,i) => source.Skip(i * chunkSize).Take(chunkSize));
}
Run Code Online (Sandbox Code Playgroud)

然后你可以sequence.Split(3)用来得到你想要的东西.

(你可以把它命名为'slice'或'chunk',如果你不喜欢'split'已经为字符串定义了.'Split'正是我碰巧称之为我的.)

  • 解决方案的唯一问题是它将遍历源n + 1次,其中n是块的数量.从性能角度来看,这是有问题的,并且处理无法重新枚举的源. (6认同)
  • @Alex当然可以!假设您的集合长度为9项,并且您希望将其拆分为3个组.所有表达式确实可以确定要创建的组数.正如你所看到的,我只对`Where`和`Select`中的索引感兴趣.我从'Where`中的索引'0-8'变为'Select'中的'0-2',因为`Where`子句将只返回9个项目中的3个(检查结果为'Enumerable.Range (0,9).选择((x,i)=> i%3)`进行证明!).所以我首先跳过0(0*3)并取3,然后跳过3(1*3)然后取3然后跳过6(2*3)并取3! (2认同)

Arn*_*sen 15

灵感来自@ dicegiuy30的实现,我想创建一个只迭代源一次并且不在内存中构建整个结果集来补偿的版本.我想出的最好的是:

public static IEnumerable<IEnumerable<T>> Split2<T>(this IEnumerable<T> source, int chunkSize) {
    var chunk = new List<T>(chunkSize);
    foreach(var x in source) {
        chunk.Add(x);
        if(chunk.Count <= chunkSize) {
            continue;
        }
        yield return chunk;
        chunk = new List<T>(chunkSize);
    }
    if(chunk.Any()) {
        yield return chunk;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样我就可以按需构建每个块.我希望我也应该尽可能地避免这种情况List<T>,但也没有想到这一点.

  • +1这似乎非常有效,但我认为它在以下行中有一个错误:`if(chunk.Count <= chunkSize)`正确的行如下:`if(chunk.Count <chunkSize)` (2认同)