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