2011-01-06 14 views
9

を使用してリストを分割します。基本的には、リストを繰り返し処理し、条件が満たされるまで要素を1つのリストに追加し、残りを別のリストに追加します。は、私は次のコードをしたLINQ

linqを使用して? CheckCondition()は高価であり、リストが巨大になる可能性があるので、リストを2回繰り返すことはしません。

+1

が、 '(e.Current)場合、'意味がありません。私はあなたが最後の 'MoveNext'呼び出しの値を(最初のループから)保存し、それが成功したかどうかを調べることを検討していると思います。 – Ani

+0

ループ外のアイテムを処理するのではなく、CheckConditionがtrueになったら、e.Currentを 'b'に追加するように更新しました。 – Anonym

+0

はい、今は明らかです。 – Ani

答えて

7

は二度リストを列挙するために起こっているソリューションですが、それは条件をもう一度チェックしませんので、それはより速くする必要があります:

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

someListIList<T>の場合、各項目は実際には1回のみ列挙されるため、罰金は発生しません。
それが実際だろう

(これについてJon Skeet's articleを参照)を使用すると、簡単にこの最適化を使用して、独自のSkipメソッドを実装することができしかし...私はSkipIList<T>の場合のために最適化されていたと思ったが、どうやらそうではありませんよりエレガントな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の' IList 'についてあなたが話す最適化を持っていません。 – Ani

+0

アニは正しいです。 'Skip'は' IList 'に対して特に最適化されていないので、リストの最初の部分は常に2回トラバースされます。 – LukeH

+0

@Ani、私はちょうど、IListのために最適化されていないようにチェック...私は私の答えを更新します。 –

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'を2回(リストごとに1回)設定します。 – Gabe

+0

@Gabe:ありがとう。私が本当に避けたかったのは、ソースを2回反復し、 'CheckCondition'の冗長な計算でした。両方の要件が問題に記載されています。 OPが旗に基づいて冗長分枝に問題があったとは思わなかった。 :) – Ani

1

これは、複数回最初のリスト内のアイテムの上に行くことになりますが、初回のみCheckConditionを通じて呼び出す:

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

a.Count()は最初のクエリを列挙しますが、結果を取得するには再び*列挙する必要があります...最後にToListを呼び出す必要があります(そして、私の解決策になります) –

2

someListコンクリートList<T>ある場合、これが唯一のシングルが必要になります

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

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 
関連する問題