2009-08-28 64 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); 
    } 
} 
相關問題