How to get a flattened list from a nested class List <T>?

I have a question using the same examples - this question focuses on another problem. Given the following classes:

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

If Family has a method defined as such:

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

I need to execute a predicate on a flattened list of Person . In the above example, SelectMany does not smooth the list as I hoped. The above will not actually compile because the inference type cannot be defined.

How can I make the Family.Person collection become one flattened Person list?

+7
source share
5 answers
 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); } 
+5
source

As far as I know, the easiest way to achieve this is to use an assistant.

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

Then you can run your predicate against this list:

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

SelectMany only aligns one level of the hierarchy:

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

In fact, you may need an arbitrary hierarchy depth. This is best done through recursion (Untested).

 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; } } } 
+4
source

You do not need both SelectMany and yield return - you need this or that:

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

OR

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

family.Person already a flattened list; no need to call SelectMany on it.

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

Alternatively, you can simply do the following:

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

All Articles