2011-01-06 48 views
9

名單我有下面的代碼:分割使用LINQ

var e = someList.GetEnumerator(); 
    var a = new List<Foo>(); 
    var b = new List<Foo>(); 
    while(e.MoveNext()) { 
    if(CheckCondition(e.Current)) { 
     b.Add(e.Current); 
     break; 
    } 
    a.Add(e.Current); 
} 

while(e.MoveNext()) 
    b.Add(e.Current) 

這看起來醜陋。基本上,遍歷一個列表並添加元素到一個列表中,直到某些條件啓動,並將其餘列表添加到另一個列表中。

有沒有更好的方法,例如使用linq? CheckCondition()很昂貴,列表可能很大,所以我寧願不做任何迭代列表兩次的任何事情。

+1

我理解你的代碼,但如果'(e.Current)'沒有意義。我認爲你正在尋找存儲最後一次MoveNext調用的值(從第一個循環開始),並檢查它是否成功。 – Ani 2011-01-06 01:24:56

+0

經過更新以使其更有意義(即,一旦CheckCondition爲true,則將「e.Current」添加到'b',而不是在循環外處理該項目。 – Anonym 2011-01-06 01:47:51

+0

是的,現在很清楚。 – Ani 2011-01-06 01:59:01

回答

7

這裏的,那將枚舉列表兩次的解決方案,但它不會檢查的情況,第二次,所以它應該是更快:

var a = someList.TakeWhile(x => !CheckCondition(x)).ToList(); 
var b = someList.Skip(a.Count).ToList(); 

如果 someList執行 IList<T>,則每個項目只會被枚舉一次,所以不會有任何處罰。
我想Skip是爲IList<T>的情況下進行了優化,但顯然這不是......不過,你可以很容易地實現使用這種優化自己Skip方法(見Jon Skeet's article這個)

這實際上是如果更優雅有一個TakeUntil方法......我們可以很容易地創建它:

public static IEnumerable<TSource> TakeUntil<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{ 
    foreach(var item in source) 
    { 
     if (predicate(item)) 
      break; 
     yield return item; 
    } 
} 

使用這種方法,代碼變爲:

var a = someList.TakeUntil(CheckCondition).ToList(); 
var b = someList.Skip(a.Count).ToList(); 
+5

+1:很好,但是我認爲* Skip從.NET 4開始並沒有你提到的IList 的優化。但是不確定。 – Ani 2011-01-06 01:16:06

+0

Ani是對的。 'Skip'對IList 沒有特別的優化,所以列表的第一部分總是被遍歷兩次。 – LukeH 2011-01-06 01:19:22

+0

@Ani,我剛剛檢查過,它似乎沒有爲IList優化...我會更新我的答案。 – 2011-01-06 01:21:58

2

就我個人而言,我不認爲這裏有任何需要LINQ。

我會做這樣的事情:

bool conditionHit = false; 

foreach (var item in someList) 
{ 
    if (!conditionHit) 
     conditionHit = CheckCondition(item); 

    var listToBeAdded = conditionHit ? b : a; 
    listToBeAdded.Add(item); 
} 
+2

我比OP的代碼更好,因爲它沒有可疑的if(e.Current)'。理想情況下,您可以設置兩次listTobeAdded(每個列表一次),而不是每次迭代計算一次。 – Gabe 2011-01-06 01:46:57

+0

@加貝:謝謝。我真正想要避免的是迭代源兩次和'CheckCondition'多餘的計算;這些要求都在問題中說明。沒想到操作系統在基於標誌的冗餘分支方面有任何問題。 :) – Ani 2011-01-06 01:58:19

1

這將最終會在項目中的第一個列表不止一次,但只能通過CheckCondition調用第一次:

var a = someList.TakeWhile(e => !CheckCondition(e)); 
var b = someList.Skip(a.Count()); 
+2

a.Count()將要列舉的第一個查詢,這將需要再次列舉*得到的結果......你需要調用ToList末(你會正好與我的解決方案最終) – 2011-01-06 01:17:36

2

如果someList是一個具體的List<T>那麼這將只需要一個通過每個元素:

var a = someList.TakeWhile(x => !CheckCondition(x)).ToList(); 
var b = someList.GetRange(a.Count, someList.Count - a.Count); 
4

我不想改變Ani's answer,但這裏有一個輕微的簡化。

var listToBeAdded = a; 
foreach (var item in someList) 
{ 
    if (listToBeAdded == a && CheckCondition(item)) 
     listToBeAdded = b; 

    listToBeAdded.Add(item); 
} 
+1

+1 - 很好的回答。 – ChaosPandion 2011-01-06 03:06:59

0

試試這個(不重複使用LINQ的內置的方法(倒帶迭代器)已知的),剛剛重新使用OP的邏輯(我相信是高性能的,它不會在下次重新評估條件名單的一半),並在一個整潔的擴展方法包裝它,元組:

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



namespace Craft 
{ 
    class Act 
    { 
     static void Main(string[] args) 
     { 

      var a = new List<string> 
       { "I", "Love", "You", "More", "Today", "Than", "Yesterday" }; 

      var tx = a.SplitByCondition(s => s == "More"); 

      foreach (var s in tx.Item1) 
       Console.WriteLine("First Half : {0}", s); 

      foreach (var s in tx.Item2) 
       Console.WriteLine("Second Half : {0}", s); 

      Console.ReadLine();      
     } 

    }//Act 

    public static class Helper 
    { 

     public static Tuple<List<T>, List<T>> SplitByCondition<T> 
      (this IEnumerable<T> t, Func<T, bool> terminator) 
     { 


      var tx = new Tuple<List<T>, List<T>> 
          (new List<T>(), new List<T>()); 

      var iter = t.GetEnumerator(); 

      while (iter.MoveNext()) 
      { 
       if (terminator(iter.Current)) 
       { 
        tx.Item2.Add(iter.Current); 
        break; 
       } 

       tx.Item1.Add(iter.Current); 
      } 

      while (iter.MoveNext()) 
       tx.Item2.Add(iter.Current); 

      return tx; 
     }  

    }//Helper 

}//Craft 

輸出:

First Half : I 
First Half : Love 
First Half : You 
Second Half : More 
Second Half : Today 
Second Half : Than 
Second Half : Yesterday