0

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

7
  • Do you have any restriction on a nesting depth? Commented Mar 17, 2016 at 10:25
  • FacetStorage.Where(p => p.IsSelectedFacet); Commented Mar 17, 2016 at 10:26
  • @Valentin no, there is no restriction on a nesting depth Commented Mar 17, 2016 at 10:27
  • @Jodrell yeah, I've tried coll.Where(p => p.IsSelectedPerson);, but this query just takes upper objests, not nested objects. Commented Mar 17, 2016 at 10:37
  • 2
    @StepUp, first you need to "flatten" the nested people. There are several ways to do this, I prefer a linq way that doesn't involve allocating an arbritraily long list. Commented Mar 17, 2016 at 10:45

5 Answers 5

4

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())
Sign up to request clarification or add additional context in comments.

8 Comments

@StepUp I've added duplicates removing.
I've tried your solution, however there are duplicates.
@StepUp What do you mean by duplicates? With the same name? or object with nested object? Are you running the last query?
with the same Name. yeah, I've tried your last query.
@StepUp Im running this code on my machine, and it works fine. Can you share what are you running?
|
3

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)

8 Comments

I cannot figure out, how can I iterate through nested persons? could you provide complete code?
ok, but how can I take nested person? sorry for my dummy questions:). please, provide full code
the Person temp is going through your nested Persons and returns the item in case IsSelectedPerson - this is the full code
@StepUp As I consider, you should use SelectMany method for the list. list.SelectMany(x=> GetIsSelectedPerson(x))
a lot of duplicate objects. I would like just single objects. Is it possible without duplicates? thanks in advance
|
2

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

2 Comments

thanks for your reply, however, I cannot change my model class.
@StepUp Maybe an extension method could do it? And pass the list as a parameter to it
1

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

Comments

1

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

9 Comments

I am trying your solution, however p does not have Person field. I mean this: .Concat(p.Person. p just has IsSelectedPerson and Name.
@StepUp, try with a lower case p, is the Person nested in the Person publically accesible?
thanks, I've edited to public. However, there are many duplicates. How can I remove it?
Distinct will help but you'll need to Equals and GetHashCode if you want different instances to be the same
sorry for misleading, I mean that I would like to get just objects with Distinct Name property.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.