2010-12-28 11 views
52

Kann ich eine IEnumerable<T> in zwei IEnumerable<T> mit LINQ und nur eine einzige Abfrage/LINQ-Anweisung teilen?Kann ich IEnumerable nach zwei Booleschen Kriterien ohne zwei Abfragen in zwei aufteilen?

Ich möchte vermeiden, zweimal durchlaufen die IEnumerable<T>. Ist es zum Beispiel möglich, die letzten beiden Aussagen so zu kombinieren, dass allValues ​​nur einmal durchlaufen wird?

IEnumerable<MyObj> allValues = ... 
List<MyObj> trues = allValues.Where(val => val.SomeProp).ToList(); 
List<MyObj> falses = allValues.Where(val => !val.SomeProp).ToList(); 

Antwort

52

Sie können diese verwenden:

var groups = allValues.GroupBy(val => val.SomeProp); 

sofortige Auswertung Um zu erzwingen, wie in Ihrem Beispiel:

var groups = allValues.GroupBy(val => val.SomeProp) 
         .ToDictionary(g => g.Key, g => g.ToList()); 
List<MyObj> trues = groups[true]; 
List<MyObj> falses = groups[false]; 
+1

elegant! Ich kann immer groups.ContainsKey() oder groups.TryGetValue() verwenden, um Fälle zu behandeln, in denen der Schlüssel fehlt. – SFun28

+3

Dies stürzt ab, wenn ein Schlüssel (wahr oder falsch) nie verwendet wird. z.B. Wenn SomeProp immer wahr ist, stürzt groups [false] mit Exception ab. "Der angegebene Schlüssel war nicht im Wörterbuch vorhanden." – buffjape

+0

GroupBy verwendet die Sortierung, um Gruppen zu erstellen. Ich weiß nicht, ob es besser ist, mit Scan zwei Mal iterierbar zu bleiben, als eine Art davon zu machen. – ejmarino

48

einige Leute wie Wörterbücher, aber ich ziehe Lookups durch das Verhalten, wenn ein Schlüssel fehlt.

IEnumerable<MyObj> allValues = ... 
ILookup<bool, MyObj> theLookup = allValues.ToLookup(val => val.SomeProp); 

    //does not throw when there are not any true elements. 
List<MyObj> trues = theLookup[true].ToList(); 
    //does not throw when there are not any false elements. 
List<MyObj> falses = theLookup[false].ToList(); 

Leider zählt dieser Ansatz zweimal - einmal zum Erstellen der Suche, dann einmal zum Erstellen der Listen.

Wenn Sie nicht wirklich Listen benötigen, können Sie diese zu einer einzigen Iteration get down:

IEnumerable<MyObj> trues = theLookup[true]; 
IEnumerable<MyObj> falses = theLookup[false]; 
+4

+1 für Vorschläge zum Nachschlagen und zum Erkennen des potenziellen Problems mit fehlenden Schlüsseln beim Verwenden von Dictionary. –

+0

Ich denke IGrouping sollte IEnumerable im zweiten Beispiel sein? IGrouping kompiliert nicht und scheint das Ergebnis der GroupBy-Erweiterungsmethode zu sein. Oder ist eine Umwandlung in IGouping erforderlich? – SFun28

+0

Ich bevorzuge 'ToLookup' auch. – leppie

3

Copy Pasta Erweiterungsmethode für Ihre Bequemlichkeit.

public static void Fork<T>(
    this IEnumerable<T> source, 
    Func<T, bool> pred, 
    out IEnumerable<T> matches, 
    out IEnumerable<T> nonMatches) 
{ 
    var groupedByMatching = source.ToLookup(pred); 
    matches = groupedByMatching[true]; 
    nonMatches = groupedByMatching[false]; 
} 

Oder mit Tupeln in C# 7,0

public static (IEnumerable<T> matches, IEnumerable<T> nonMatches) Fork<T>(
    this IEnumerable<T> source, 
    Func<T, bool> pred) 
{ 
    var groupedByMatching = source.ToLookup(pred); 
    return (groupedByMatching[true], groupedByMatching[false]); 
} 

// Ex. 
var numbers = new [] { 1, 2, 3, 4, 5, 6, 7, 8 }; 
var (numbersLessThanEqualFour, numbersMoreThanFour) = numbers.Fork(x => x <= 4);