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

StepUp
  • 36,391
  • 15
  • 88
  • 148
  • Do you have any restriction on a nesting depth? – Valentin Mar 17 '16 at 10:25
  • `FacetStorage.Where(p => p.IsSelectedFacet);` – Jodrell Mar 17 '16 at 10:26
  • @Valentin no, there is no restriction on a nesting depth – StepUp Mar 17 '16 at 10:27
  • @Jodrell yeah, I've tried `coll.Where(p => p.IsSelectedPerson);`, but this query just takes upper objests, not nested objects. – StepUp Mar 17 '16 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. – Jodrell Mar 17 '16 at 10:45
  • If you have already solved the recursive list, you can use groupBy Name with linq, and then select first, like in this link: http://stackoverflow.com/questions/19406242/select-distinct-using-linq – jparaya Mar 17 '16 at 13:27
  • @downvoter what is the reason to downvote? it is really interesting:). Could you provide explanation? – StepUp Mar 24 '16 at 07:00

5 Answers5

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())
Sulthan
  • 128,090
  • 22
  • 218
  • 270
Valentin
  • 5,380
  • 2
  • 24
  • 38
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)
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
fubo
  • 44,811
  • 17
  • 103
  • 137
  • I cannot figure out, how can I iterate through nested persons? could you provide complete code? – StepUp Mar 17 '16 at 10:48
  • ok, but how can I take nested person? sorry for my dummy questions:). please, provide full code – StepUp Mar 17 '16 at 10:55
  • the `Person temp` is going through your nested `Persons` and returns the item in case `IsSelectedPerson` - this is the full code – fubo Mar 17 '16 at 11:00
  • @StepUp As I consider, you should use `SelectMany` method for the `list`. `list.SelectMany(x=> GetIsSelectedPerson(x))` – Valentin Mar 17 '16 at 11:02
  • a lot of duplicate objects. I would like just single objects. Is it possible without duplicates? thanks in advance – StepUp Mar 17 '16 at 11:34
  • should be `GetIsSelectedPerson(rootPerson).Distinct()` with an overridden `==` operator – fubo Mar 17 '16 at 11:36
  • Please, could you show you thoughts expressed in code?:) I am really not cool programmer:). How can I connect `Distinct()` and overridden `==` operator? Thanks in advance – StepUp Mar 17 '16 at 12:28
  • here is the code for a custom distinct http://stackoverflow.com/questions/9601674/creating-a-distinct-list-of-custom-type-in-c-sharp – fubo Mar 17 '16 at 12:32
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();
   }
}
Alexander Derck
  • 13,818
  • 5
  • 54
  • 76
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();
Jodrell
  • 34,946
  • 5
  • 87
  • 124
  • 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 Mar 17 '16 at 10:53
  • @StepUp, try with a lower case `p`, is the Person nested in the Person publically accesible? – Jodrell Mar 17 '16 at 10:55
  • thanks, I've edited to `public`. However, there are many duplicates. How can I remove it? – StepUp Mar 17 '16 at 12:26
  • `Distinct` will help but you'll need to `Equals` and `GetHashCode` if you want different instances to be the same – Jodrell Mar 17 '16 at 12:43
  • sorry for misleading, I mean that I would like to get just objects with Distinct `Name` property. – StepUp Mar 17 '16 at 12:45
  • `Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)` – StepUp Mar 17 '16 at 12:56
  • @SetUp are all `Person` with the same name the same or, if not, which `Person` do you want? – Jodrell Mar 17 '16 at 12:58
  • yeah, all `Person` with the same name is the same. So I just want to delete all persons with the same name, and to get a result with the unique `Name` of `Person`. Please, see updated section of my question. sorry for misleading. – StepUp Mar 17 '16 at 13:16
  • @StepUp them implement `Equals` and `GetHashCode` like in Valentin's answer and just do 'flattener(person).Where(p => p.IsSelectedPerson).Distinct();` – Jodrell Mar 17 '16 at 14:49
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;
     }
    
jparaya
  • 1,331
  • 11
  • 15