2012-01-12 16 views
7

Tengo un question using these same examples - esta pregunta se centra en un problema diferente. Dadas las siguientes clases:Cómo obtener una lista aplanada de la clase anidada Lista <T>?

[XmlRoot] 
    public class Family { 

     [XmlElement] 
     public List<Person> Person; 
    } 

    public class Person { 

     [XmlAttribute("member")] 
     public MemberType Member { get; set; } 

     [XmlAttribute("id")] 
     public int Id { get; set; } 

     [XmlElement] 
     public string Surname { get; set; } 

     [XmlElement] 
     public string Forename { get; set; } 

     [XmlElement("Person")] 
     public List<Person> People; 
    } 

    public enum MemberType { 
     Father, 
     Mother, 
     Son, 
     Daughter 
    } 

Si Family tiene un método definido como tal:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    // how do I get SelectMany to flatten the list? 
    foreach (var p in family.Person.SelectMany(p => p)) { 
     if(predicate(p)) { 
      yield return p; 
     } 
    } 
} 

que necesitan para poder ejecutar el predicado sobre una lista de aplanado Person. En el ejemplo anterior, SelectMany, no está preparando la lista como esperaba. Lo anterior en realidad no se compilará porque el tipo inferido no se puede determinar.

¿Cómo puedo hacer que la colección Family.Person se convierta en una lista aplanada de Persona?

+0

¿Usted intentó 'p => p.People'? – jvstech

+0

Si tiene bucles en su estructura de datos, puede utilizar esta solución: http://stackoverflow.com/questions/141467/recursive-list-flattening/24747394#answer-24747394 – Aidin

Respuesta

5
public IEnumerable<Person> Find(IEnumerable<Person> input, Func<Person, bool> predicate) { 
    return input.Select(p => 
     { 
      var thisLevel = new List<Person>(); 
      if(predicate(p)) 
       thisLevel.Add(p); 

      return thisLevel.Union(Find(p.People ?? new List<Person>(), predicate)); 
     } 
    ).SelectMany(p => p); 
} 
+0

+1 para detectar la estructura recursiva de datos, la pregunta no hizo que este punto fuera lo suficientemente obvio :) – MattDavey

+2

Recomiendo Concat sobre Union ya que no hay necesidad de seleccionar duplicados cruzados. –

2

family.Personya es una lista aplanada; no es necesario llamar al SelectMany.

foreach (var p in family.Person) { 
    if(predicate(p)) { 
     yield return p; 
    } 
} 

Además, puede más simplemente hacer:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    return family.Person.Where(predicate); 
} 
+0

observe que Person tiene una lista anidada * *. Es ** no ** una lista aplanada, es una estructura de datos recursiva. – MattDavey

+1

@Matt - No vi eso, especialmente considerando el código original de OP de 'family.Person.SelectMany (p => p)' Editaría para corregirlo, pero parece que Petr ya lo sacó del parque –

3

que no es necesario tanto SelectMany y yield return - necesita una o la otra:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    foreach (var p in family.Person) { 
     if(predicate(p)) { 
      yield return p; 
     } 
    } 
} 

O

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    return family.Person.Where(p => predicate(p)); // Can be written simply as Where(predicate) 
} 
+1

* 'p => predicado (p)' * se puede reducir a solo * 'predicado' * - atm usted está envolviendo uno * Func * con otro. – MattDavey

+1

@MattDavey Agregué un contenedor para eliminar la "magia" de la expresión. Agregué una línea de comentario para decir que se puede simplificar. Es solo que mirar una cláusula 'Where' sin' => 'puede ser confuso para las personas con relativamente poca experiencia LINQ. – dasblinkenlight

+0

Sí, creo que es una cosa pertinente :) – MattDavey

6

Que yo sepa, la manera más fácil de lograr esto es usar un ayudante.

private List<Person> FlattenTree(Person person) 
    { 
     var accumulator = new List<Person>(); 
     FlattenPersonHelper(person, accumulator); 

     return accumulator; 
    } 


    private void FlattenPersonHelper(Person person, List<Person> accumulator) 
    { 
     accumulator.Add(person); 

     foreach (var child in person.People) 
     { 
      FlattenPersonHelper(child, accumulator); 
     } 
     return; 
    } 

continuación, podrá ejecutar el predicado en contra de esta lista:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    var familyRoot = new Person() { People = family.Person }; 
    return FlattenTree(familyRoot).Where(predicate); 
} 
+2

+1 Me gusta - ¡también Bienvenido a stackoverflow! –

+1

Gracias! ¡y gracias! PD. Uso este patrón con la suficiente frecuencia como para haber escrito una versión genérica como método de extensión. Súper útil. –

4

SelectMany sólo se aplana un nivel de jerarquía:

public IEnumerable<Person> FindLevel2 (Func<Person, bool> predicate) 
{ 
    return family.Person.SelectMany(p => p.People).Where(predicate); 
} 

En realidad, puede que desee un paseo profundidad arbitraria de la jerarquía Eso se hace mejor por recursión (no probado).

public IEnumerable<Person> Find(Func<Person, bool> predicate) 
{ 
    foreach(Person p in family.Person) 
    { 
    IEnumerable<Person> result = FindFromPerson(p); 
    foreach(Person x in result) 
    { 
     yield return x; 
    } 
    } 
} 

public IEnumerable<Person> FindFromPerson(Person p, Func<Person, bool> predicate) 
{ 
    if predicate(p) 
    { 
    yield return p; 
    } 
    foreach(Person child in p.People) 
    { 
    IEnumerable<Person> childResults = FindFromPerson(child); 
    foreach(Person x in childResults) 
    { 
     yield return x; 
    } 
    } 
} 
Cuestiones relacionadas