StepUp
StepUp

Reputation: 38164

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

Upvotes: 0

Views: 2077

Answers (5)

fubo
fubo

Reputation: 45967

Here is another approach to yield your list of items:

IEnumerable<Person> GetIsSelectedPerson(Person p)
{
    Person temp = p;
    while (temp != null)
    {
        if (temp.IsSelectedPerson)
        {
            yield return temp;
        }
        temp = temp.person;
    }           
}

Usage:

IEnumerable<Person> Result = GetIsSelectedPerson(rootPerson)

Upvotes: 3

Valentin
Valentin

Reputation: 5488

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

Upvotes: 4

Jodrell
Jodrell

Reputation: 35716

Do this to flatten the people,

Func<Person, IEnumerable<Person>> flattener = null;
flattener = p => new[] { p }
    .Concat(
        p.person == null 
            ? Enumerable.Empty<Person>()
            : (new [] { p.Person }).SelectMany(child => flattener(child)));

So you can do this,

flattener(person).Where(p => p.IsSelectedPerson);

Following you comments, what you possibly want is,

flattener(person)
   .Where(p => p.IsSelectedPerson)
   .Select(p => p.Name)
   .Distinct();

Upvotes: 1

jparaya
jparaya

Reputation: 1339

Since you don't know the level of persons in the chain, the best is to use recursion. Two simple solutions (suppose you add the methods on Person class)

  1. Create a method that receives a list, so you can fill it in the recursive call: List completeList = new List(); list[0].GetCompleteList(completeList); list[1].GetCompleteList(completeList);

    public void GetCompleteList(List<Person> personsList)
    {
        personsList.Add(this);
        if (person != null)
        {
            person.GetCompleteList(personsList);
        }
    }
    
  2. The same, without parameter

    List<Person> completeList = new List<Person>();
    completeList.AddRange(list[0].GetCompleteList());
    completeList.AddRange(list[1].GetCompleteList());
    
    // Another way: with linq
    var myPersons  list.SelectMany(m => m.GetCompleteList());
    
    public List<Person> GetCompleteList()
     {
         List<Person> returnList = new List<Person>();
         returnList.Add(this);
         if (person != null)
         {
             returnList.AddRange(person.GetCompleteList());
         }
         return returnList;
     }
    

Upvotes: 1

Alexander Derck
Alexander Derck

Reputation: 14498

I would use some kind of visiting pattern with recursion to visit all the nested Persons:

class Person
{
   public static List<Person> selectedPersons;
   Person person;
   public Person(Person _nestedPerson)
   {
       if(selectedPersons == null)
         selectedPersons = new List<Person>();
       person = _nestedPerson;
   }

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

   public void Visit()
   {
       if(this.IsSelectedPerson)
         selectedPersons.Add(this);
       if(this.person != null)
         this.person.Visit();
   }
}

Upvotes: 2

Related Questions