2011-10-03 187 views
3

可以在下面的被改寫,這樣是使用LINQ,(而是一個這些老式foreach循環)拆分列表到列表的列表,一個元素分裂

IEnumerable<IEnumerable<T>> SplitIntoSections<T>(IEnumerable<T> content, 
    Func<T, bool> isSectionDivider) 
{ 
    var sections = new List<List<T>>(); 
    sections.Add(new List<T>()); 
    foreach (var element in content) 
    { 
     if (isSectionDivider(element)) 
     { 
      sections.Add(new List<T>()); 
     } 
     else 
     { 
      sections.Last().Add(element); 
     } 
    } 

    return sections; 
} 

我想我幾乎有一這樣做的方式,(它涉及FSharp colections),當我意識到它可以用foreach循環完成。

+1

我認爲它會涉及到'X組by'語法,但我不夠好,在LINQ張貼正確回答。 :( –

+0

我中央社時空視點2級的解決方案中,一個由不使用組(和是> O(n)的(儘管可能只O(2N)), 的其他用途骨料,並且由於集料是LINQ的F#的折,它必須能夠表達每一個操作,你可以做一個列表,如果真 –

+0

指正,添加到組?從我所看到的,分隔留出分隔。 –

回答

2

你不想在這裏使用LINQ。如果不做一些粗糙的事情,你將無法按順序進行排序和分組。

最簡單的事情做的是把你的代碼,使其使用yield statement推遲執行。一個簡單的方法來做到這一點如下:

IEnumerable<IEnumerable<T>> SplitIntoSections<T>(this IEnumerable<T> source, 
    Func<T, bool> sectionDivider) 
{ 
    // The items in the current group. 
    IList<T> currentGroup = new List<T>(); 

    // Cycle through the items. 
    foreach (T item in source) 
    { 
     // Check to see if it is a section divider, if 
     // it is, then return the previous section. 
     // Also, only return if there are items. 
     if (sectionDivider(item) && currentGroup.Count > 0) 
     { 
      // Return the list. 
      yield return currentGroup; 

      // Reset the list. 
      currentGroup = new List<T>(); 
     } 

     // Add the item to the list. 
     currentGroup.Add(item); 
    } 

    // If there are items in the list, yield it. 
    if (currentGroup.Count > 0) yield return currentGroup; 
} 

這裏有一個問題;對於非常大的組,將子組存儲在列表中效率低下,它們也應該流出。你的方法存在的問題是你需要在每個項目上調用一個函數;它會干擾流操作,因爲一旦發現分組,就無法將流重置(因爲實際上需要產生結果的兩個方法)。

+0

我開始用這個當你發佈你的答案玩。完全同意。不會像倍受打擊LINQ如果if語句分支的分配是相等的。+1 –

0

你可以使用一個副作用,這是隻有一個明確的區域內使用......這是很臭,但是:

int id = 0; 
return content.Select(x => new { Id = isSectionDivider(x) ? id : ++id, 
           Value = x }) 
       .GroupBy(pair => pair.Id, pair.Value) 
       .ToList(); 

必須有一個更好的選擇,但... Aggregate將得到你那裏如果有必要...

return content.Aggregate(new List<List<T>>(), (lists, value) => { 
          if (lists.Count == 0 || isSectionDivider(value)) { 
           lists.Add(new List<T>()); 
          }; 
          lists[lists.Count - 1].Add(value); 
          return lists; 
         }); 

...但總體來說,我與casperOne同意,這是LINQ之外最好的處理情況。

+1

大聲笑。我看着這個答案,它說「編輯22小時前」由「casperOne」和答案中的最後一句是「......但總體上我同意casperOne」...... –

0

這裏是一個低效率的,但純LINQ的解決方案:

var dividerIndices = content.Select((item, index) => new { Item = item, Index = index }) 
          .Where(tuple => isSectionDivider(tuple.Item)) 
          .Select(tuple => tuple.Index); 


return new[] { -1 } 
     .Concat(dividerIndices) 
     .Zip(dividerIndices.Concat(new[] { content.Count() }), 
      (start, end) => content.Skip(start + 1).Take(end - start - 1)); 
0

好吧,我用一個LINQ方法在這裏,雖然這不是特別是在你的問題的精神,我認爲:

static class Utility 
{ 
    // Helper method since Add is void 
    static List<T> Plus<T>(this List<T> list, T newElement) 
    { 
     list.Add(newElement); 
     return list; 
    } 

    // Helper method since Add is void 
    static List<List<T>> PlusToLast<T>(this List<List<T>> lists, T newElement) 
    { 
     lists.Last().Add(newElement); 
     return lists; 
    } 

    static IEnumerable<IEnumerable<T>> SplitIntoSections<T> 
     (IEnumerable<T> content, 
      Func<T, bool> isSectionDivider) 
    { 
     return content.Aggregate(      // a LINQ method! 
      new List<List<T>>(),      // start with empty sections 
      (sectionsSoFar, element) => 
      isSectionDivider(element) 
       ? sectionsSoFar.Plus(new List<T>()) 
        // create new section when divider encountered 

       : sectionsSoFar.PlusToLast(element) 
        // add normal element to current section 
      ); 
    } 
} 

我相信你會注意到完全沒有錯誤檢查,你應該決定使用此代碼...