2010-12-28 6 views
52

Czy mogę podzielić IEnumerable<T> na dwa IEnumerable<T> przy użyciu LINQ i tylko pojedyncze zapytanie/LINQ?Czy można podzielić IEnumerable na dwa według kryteriów boolowskich bez dwóch kwerend?

Chcę uniknąć wielokrotnego powtarzania przez IEnumerable<T>. Na przykład, czy możliwe jest połączenie dwóch ostatnich instrukcji poniżej, aby wszystkie wartości były wykonywane tylko raz?

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

Odpowiedz

52

Można to wykorzystać:

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

Aby wymusić natychmiastową ocenę jak w przykładzie:

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

elegancki! Zawsze mogę użyć groups.ContainsKey() lub groups.TryGetValue() do obsługi przypadku, w którym brakuje klucza. – SFun28

+3

Powoduje to awarię, jeśli jeden klawisz (true lub false) nigdy nie jest używany. na przykład jeśli SomeProp ma zawsze wartość true, grupy [false] ulegną awarii z wyjątkiem "Podany klucz nie występuje w słowniku" – buffjape

+0

GroupBy użyj sortowania, aby utworzyć grupy. Nie wiem, czy lepiej trzymać ze skanem dwa razy, niż robić coś w tym rodzaju. – ejmarino

48

Niektórzy ludzie lubią słowniki, ale wolę Lookup spowodowany zachowaniem kiedy brakuje klucza.

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(); 

Niestety, to podejście wylicza dwukrotnie: raz, aby utworzyć odnośnik, a następnie raz, aby utworzyć listy.

Jeśli naprawdę nie trzeba list, można to dostać się do jednej iteracji:

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

+1 za sugerowanie wyszukiwania oraz za zauważenie potencjalnego problemu z brakującymi kluczami podczas korzystania ze słownika. –

+0

Myślę, że IGrouping powinno być IEnumerable w drugim przykładzie? IGrouping nie kompiluje się i wydaje się być wynikiem metody rozszerzenia GroupBy. A może wymagany jest rzut na IGrouping? – SFun28

+0

Wolę też "ToLookup". – leppie

3

Copy metodę rozszerzenia pasta dla Twojej wygody.

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

lub używając krotki w 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); 
Powiązane problemy