2009-08-28 58 views

回答

28
var result = sequence.Select((s, i) => new { Value = s, Index = i }) 
        .GroupBy(item => item.Index/3, item => item.Value); 

注意这将返回一个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>>(); 
+0

这是unbelivably快,感谢 – 2009-08-28 21:36:39

+2

是否具有的GroupBy遍历整个序列,你得到任何结果之前,或者你还在这里得到延迟执行? – 2009-11-14 00:27:34

+0

@Don Kirkby:对于LINQ to Objects,'.GroupBy'不会枚举序列。它一旦枚举了'.GetEnumerator'就会枚举整个序列(例如,在'foreach'中使用的时候)。 – 2009-11-14 06:19:42

0

我想出了一个不同做法。它使用了一个while迭代器,但结果像常规LINQ一样缓存在内存中,直到需要为止。
这是代码。

public IEnumerable<IEnumerable<T>> Paginate<T>(this IEnumerable<T> source, int pageSize) 
{ 
    List<IEnumerable<T>> pages = new List<IEnumerable<T>>(); 
    int skipCount = 0; 

    while (skipCount * pageSize < source.Count) { 
     pages.Add(source.Skip(skipCount * pageSize).Take(pageSize)); 
     skipCount += 1; 
    } 

    return pages; 
} 
+1

之前从未见过'Runtime.CompilerServices.Extension',所以我查了一下,MSDN说:“在C#中,你不需要使用这个属性;你应该使用'this'修饰符作为第一个参数来创建一个扩展方法。“换句话说,虽然它在功能上等价,但最好使用'public IEnumerable > Paginate (此IEnumerable source,int pageSize)'而不是属性。 – Davy8 2011-07-05 20:54:33

+0

@ Davy8:我刚刚在学习C#时提交了这个答案,所以代码是VB.NET的一个直接端口。我现在已经掌握了一些关于C#的知识,并且我知道这段代码并不是真正的“正确”。我已经更新了答案。 – 2011-07-06 02:54:09

+0

你是怎么得到'source.Count'的?它是一个可枚举的,并且在其上执行'Count()'枚举集合。 – nawfal 2013-02-18 14:06:35

20

我知道这已经回答了,但如果你打算采取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)); 
} 

然后你可以使用sequence.Split(3)来得到你想要的。 (如果你不喜欢这个,你可以命名为'slice'或'chunk','split'已经被定义为字符串,'Split'就是我碰巧称为我的)。

+0

+1。我喜欢这样一个事实,即您可以获得与我使用一行代码相同的结果。 – 2010-10-14 16:53:05

+0

这已经有一段时间了,我一直在使用你的代码(解决方案/答案/无论你怎么称呼它)一段时间,它完美的工作。我最近试图分析你的代码,我无法掌握'.Where((x,i)=> i%chunkSize == 0)'代码的一部分,它仍然可以正常工作。如果你不介意,你能解释一下你的代码对我有什么作用吗?谢谢。 – 2011-05-05 18:56:41

+1

@Alex当然!假设你的收藏有9个项目的长度,你想把它分成3个小组。所有这些表达的确是确定将要制作多少个小组。正如你所看到的,我只对'Where'和'Select'中的索引真正感兴趣。因为'Where'子句只会从9个项目中返回3个项目(Enumerable.Range的检查结果),因此我将'Where'中的索引'0-8'设置为'Select'中的'0-2' (0,9)。选择((x,i)=> i%3)'作为证明!)。所以我首先跳过0(0 * 3)并取3,然后跳过3(1 * 3)并取3然后跳过6(2 * 3)并取3! – diceguyd30 2011-05-05 20:08:18

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

这样我就建立每个块的需求。我希望我也应该避开List<T>,并且只是传播那个,但还没有弄清楚。

+1

+1是一个很好的实现。与Jon Skeet完全一样:http://code.google.com/p/morelinq/source/browse/trunk/MoreLinq/Batch.cs – diceguyd30 2012-02-06 14:05:33

+2

+1这似乎是非常有效的,但我认为它有一个错误代码如下:'if(chunk.Count <= chunkSize)' 正确的代码行如下:'if(chunk.Count 2012-05-23 19:28:47

2

使用Microsoft.Reactive你可以很简单地做到这一点,你将只遍历源代码一次。

IEnumerable<string> source = new List<string>{"1", "2", "3", "4", "5", "6"}; 

IEnumerable<IEnumerable<string>> splited = source.ToObservable().Buffer(3).ToEnumerable(); 
+1

您不需要使用ToObservable,然后返回ToEnumerable ,您可以使用与Enumerable一起使用的交互式扩展缓冲区方法。在nuget上查找Ix-Main。 – 2013-10-23 04:10:00

+0

如果使用'Reactive',则需要ToObservable。否则,您必须使用'Interactive' – Emaborsa 2016-12-22 09:18:08

2

我们可以改进@ Afshari的解决方案来做真正的懒惰评估。我们使用GroupAdjacentBy方法能产生连续的元素组具有相同的键:

sequence 
.Select((x, i) => new { Value = x, Index = i }) 
.GroupAdjacentBy(x=>x.Index/3) 
.Select(g=>g.Select(x=>x.Value)) 

由于基团,得到一个接一个,该解决方案的工作原理有效地与长或无限序列。

27

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

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

和一些测试代码:

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

+1来保留原始的可枚举性。只是我正在寻找与'BlockingCollection'一起工作的解决方案。 – 2012-09-20 09:45:30

+0

你值得+2包括测试代码。不像其他一些例子那样简洁,但在保持数字化和限制对集合的迭代方面最为精确。做得好! – mtazva 2012-09-21 21:14:38

+3

注意:理解此代码不是线程安全是至关重要的! 在将结果传递给异步代码之前,您必须同步将生成的枚举转换为具体类型,以确保批量合适地收集项目。 – mtazva 2012-09-24 19:27:37

0

迈赫达德Afshari的答案是优秀。这里是一个封装它的扩展方法:

using System.Collections.Generic; 
using System.Linq; 

public static class EnumerableExtensions 
{ 
    public static IEnumerable<IEnumerable<T>> GroupsOf<T>(this IEnumerable<T> enumerable, int size) 
    { 
     return enumerable.Select((v, i) => new {v, i}).GroupBy(x => x.i/size, x => x.v); 
    } 
} 
相关问题