2010-12-28 14 views
52

¿Puedo dividir un IEnumerable<T> en dos IEnumerable<T> usando LINQ y solo una sola consulta/instrucción LINQ?¿Puedo dividir un IEnumerable en dos por un criterio booleano sin dos consultas?

Quiero evitar repetir el IEnumerable<T> dos veces. Por ejemplo, ¿es posible combinar las dos últimas declaraciones a continuación, de modo que todos los valores solo se recorren una vez?

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

Respuesta

52

Puede utilizar esta:

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

Para forzar la evaluación inmediata como en su ejemplo:

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

elegante! Siempre puedo usar groups.ContainsKey() o groups.TryGetValue() para manejar el caso donde falta la clave. – SFun28

+3

Esto falla si una clave (verdadera o falsa) nunca se usa. p.ej. si SomeProp es siempre verdadero, los grupos [falso] se bloquearán con Exception "La clave dada no estaba presente en el diccionario" – buffjape

+0

GroupBy use sorting para hacer grupos. No sé si es mejor seguir con el escaneo iterativo dos veces que hacer una especie de prueba. – ejmarino

48

Algunas personas, como diccionarios, pero yo prefiero búsquedas debido al comportamiento cuando una la clave falta

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

Lamentablemente, este enfoque se enumera dos veces: una para crear la búsqueda y otra para crear las listas.

Si realmente no necesita listas, puede conseguir esto a una sola iteración:

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

+1 para sugerir búsqueda y para detectar el posible problema con las teclas faltantes al usar el diccionario. –

+0

Creo que IGrouping debería ser IEnumerable en el segundo ejemplo? IGrouping no compila y parece ser el resultado del método de extensión GroupBy. ¿O tal vez se requiere un elenco para IGrouping? – SFun28

+0

Prefiero 'ToLookup' también. – leppie

3

Copia método de extensión de pasta para su conveniencia.

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

O usando tuplas en 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); 
Cuestiones relacionadas