2011-01-06 12 views
9

Ich habe den folgenden Code:geteilt eine Liste mit 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) 

Diese hässlich aussieht. Im Grunde iterieren Sie durch eine Liste und fügen Elemente zu einer Liste hinzu, bis eine Bedingung eintritt, und fügen den Rest einer anderen Liste hinzu.

Gibt es einen besseren Weg, z.B. mit linq? CheckCondition() ist teuer, und die Listen können riesig sein, also würde ich lieber nichts tun, was die Listen zweimal wiederholt.

+1

ich Ihren Code zu verstehen, aber 'if (e.Current)' macht keinen Sinn. Ich denke, Sie möchten den Wert des letzten 'MoveNext'-Aufrufs (von der ersten Schleife) speichern und prüfen, ob es erfolgreich war. – Ani

+0

Aktualisiert, um mehr Sinn zu machen (d. H., E.Current zu "b" hinzufügen, sobald CheckCondition wahr ist, anstatt dieses Element außerhalb der Schleife zu behandeln – Anonym

+0

Ja, es ist jetzt klar. – Ani

Antwort

7

Hier ist eine Lösung, die die Liste zweimal aufzuzählen wird, aber es wird die Bedingung zum zweiten Mal nicht überprüfen, so sollte es schneller sein:

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

Wenn someList implementiert IList<T>, wird jedes Element tatsächlich nur einmal aufgeführt, so dass es keine Strafe geben wird.
Ich dachte Skip für den Fall IList<T> optimiert wurde, aber es ist offensichtlich nicht ... Allerdings könnte man leicht Ihre eigene Skip Methode implementieren, die diese Optimierung verwendet (siehe Jon Skeet's article darüber)

Es wäre eigentlich sein elegant, wenn es eine TakeUntil Methode ist ... wir es einfach erstellen können:

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

Mit dieser Methode wird der Code:

+5

+1: Schön, aber ich denke * Skip 'hat nicht die Optimierung, die Sie sprechen für 'IList ' von .NET 4. Nicht sicher, obwohl. – Ani

+0

Ani hat Recht. 'Skip' hat keine spezifischen Optimierungen für' IList 'so wird der erste Teil der Liste immer zweimal durchlaufen. – LukeH

+0

@Ani, ich habe gerade überprüft, es scheint nicht für IList optimiert zu sein ... Ich werde meine Antwort aktualisieren. –

2

Persönlich glaube ich nicht, dass hier LINQ benötigt wird.

ich tun würde, so etwas wie:

bool conditionHit = false; 

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

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

Ich mag das besser als der OP-Code, weil es nicht fraglich 'if (e.Current)' darin hat. Im Idealfall würden Sie "listTobeAdded" zweimal (einmal für jede Liste) festlegen, anstatt es für jede Iteration zu berechnen. – Gabe

+0

@Gabe: Danke. Was ich wirklich vermeiden wollte, war die zweimalige Wiederholung der Quelle und redundante Berechnungen von 'CheckCondition'; Beide Anforderungen sind in dem Problem angegeben. Ich dachte nicht, dass das OP irgendwelche Probleme mit redundanten Verzweigungen aufgrund einer Flagge hatte. :) – Ani

1

Dies wird nach oben gehen über die Elemente in der ersten Liste mehr als einmal beenden, sondern nur Anrufe über CheckCondition das erste Mal: ​​

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

a.Count() wird die erste Abfrage aufzählen, die * erneut * aufgezählt werden muss, um die Ergebnisse zu erhalten ... Sie müssen am Ende ToList aufrufen (und Sie werden mit genau meiner Lösung enden) –

2

Wenn someList ein Beton ist List<T> dann wird dies benötigen nur eine einzige Pass durch jedes Element:

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

ich nicht Ani's answer ändern wollte, aber hier ist eine leichte Vereinfachung.

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

    listToBeAdded.Add(item); 
} 
+1

+1 - Große Antwort. – ChaosPandion

0

die Sie interessieren (nicht Linq der integrierten Methoden der Wiederverwendung (bekannt für die Iteratoren Zurückspulen)), nur die Logik des OP Wiederverwendung (was ich glaube performant ist, ist es nicht neu bewerten Zustand auf der nächsten die Hälfte der Liste) und es in einer ordentlichen Erweiterungsmethode und Tuple Verpackung:

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 

Ausgang:

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