Search code examples
c#linq

How can I get all nested items from a collection?


I have a collection of items. The one item can have another item, and another item can have another item. So on.

I do not know how many levels of nested items can have item. The level of nested items can be defined at run-time.

class Person
{
    Person person;
    public Person(Person _nestedPerson)
    {
        person = _nestedPerson;
    }

    public bool IsSelectedPerson { get; set; }
    public string Name { get; set; }
}

and how items(Person) can be nested:

IList<Person> list = new List<Person>();            
for (int startIndex = 0; startIndex < 5; startIndex++)
{
   list.Add(new Person(new Person(new Person(new Person(null) { Name="Bill", 
        IsSelectedPerson=true})) { Name = "Jessy", IsSelectedPerson = false }) 
        { Name = "Bond", IsSelectedPerson =true});//3 nested persons
   list.Add(new Person(new Person(null) { Name = "Kendell", 
        IsSelectedPerson = true }) { Name="Rosy", IsSelectedPerson=true});//2 nested persons
   //The next time it can be just one person without nested item(person). I do not know how many items(persons) will be nested
   //list.Add(new Person(null) { Name="Rosy", IsSelectedPerson=true});
}

My goal is to take ALL objects(without duplicates) of persons(Person) who IsSelectedPerson=true?

I've played with Select()

var ee = list.Select(x=>x.IsSelectedFacet==true);//comparison should be done here

but it is not what I want, it just takes bool values.

Update:

My expected result should be have one object of Person with unique name. No matter how many there are objects with the same name. I would like to take just one object. Sorry for misleading. It should be look like this:

enter image description here


Solution

  • You can make a helper method to unwrap all nested objects

        IEnumerable<Person> UnwrapPerson(Person p)
        {
            List<Person> list = new List<Person>();
            list.Add(p);
            if (p.person != null)
                list.AddRange(UnwrapPerson(p.person));
    
            return list;
        }
    

    Or if Person class has only one nested object (Person person;) you can use a yield construction instead of the recursion

        static IEnumerable<Person> UnwrapPerson(Person p)
        {
            yield return p;
            while (p.person != null)
            {
                p = p.person;
                yield return p;
            }
        }
    

    In order to remove all duplicate persons, for example with the same name, you should implement IEqualityComparer<Person> and then use Distinct method.

    class Comparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            return string.Equals(x.Name, y.Name);
        }
    
        public int GetHashCode(Person obj)
        {
            string name = obj.Name;
            int hash = 7;
            for (int i = 0; i < name.Length; i++)
            {
                hash = hash * 31 + name[i];
            }
    
            return hash;
        }
    }
    

    So final query should be similar to:

     list.SelectMany(p => UnwrapPerson(p))
         .Where(x => x.IsSelectedPerson == true)
         .Distinct(new Comparer())