How can I get all nested items from a collection? - c#

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:

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

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)

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

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

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

Related

Order list by parent and child and parent of the child

I'm trying to order List that should look like this
Parent
Child1 (simultaneously children and parent)
Child2 (Children of Child1)
Child3
In using Class that contain information's about ID, ParentID and etc.
I'm trying to make this work using LINQ and tried different solution but no one work completely, I know that with recursively function will work (but really don't like that), can someone help me to make working with LINQ ?
i tried this code but Child2 don't appearing.
List<Person> orderedList = new List<Person>();
persons.ForEach(x => {
if (x.ParentID == 0) {
orderedList.Add(x);
orderedList.AddRange(persons.Where(child => child.ParentID == x.Id));
}
});
For those who are voting "negative" remember no one was god at programming at the beginning, if i come here that means that I'm struggle to fix the problem for x hours. And also if you think that my English is bad i know that already, I'm not born to speak English perfectly but those who wants to help will help. :)
Whole Code
public class Person{
public int Id { get; set; }
public string MenuName { get; set; }
public int? ParentID { get; set; }
public string isHidden { get; set; }
public string LinkURL { get; set; }
}
public static List<Person> AddPersons(){
var persons = new List<Person>();
using (var reader = new StreamReader(#"C:\Users\AceDuk\Desktop\Navigation.csv")){
var line = reader.ReadLine(); //Da se izbegne headerot
while ((line = reader.ReadLine()) != null){
var values = line.Split(';');
if (values[2] == "NULL") {
values[2] = "0";
}
persons.Add(new Person(){
Id = Int32.Parse(values[0]),
MenuName = values[1],
ParentID = Int32.Parse(values[2]),
isHidden = values[3],
LinkURL = values[4]
});
}
}
persons.RemoveAll(x => x.isHidden == "True"); //Izbrisi gi site sto se hidden ne gi pokazuvaj..
//persons = persons.OrderBy(x => x.MenuName).ToList(); //Ordered
persons = persons.OrderBy(x => x.LinkURL).ToList(); //Ordered
return persons;
}
static void Main(string[] args){
List<Person> persons = AddPersons();
List<Person> orderedList = new List<Person>();
persons.ForEach(x => {
if (x.ParentID == 0) {
orderedList.Add(x);
orderedList.AddRange(persons.Where(child => child.ParentID == x.Id));
}
});
foreach (var item in orderedList) {
Console.WriteLine(item.MenuName);
}
}
Create a double-ended queue (deque) data structure by extending a linked list:
public class Deque<T> : LinkedList<T> {
public void Enqueue(T item) => AddLast(item);
public T Dequeue() {
var item = First.Value;
RemoveFirst();
return item;
}
public void EnqueueRange(IEnumerable<T> items) {
foreach (var item in items)
Enqueue(item);
}
public void Push(T item) => AddFirst(item);
public T Pop() => Dequeue();
public void PushRange(IEnumerable<T> items) {
foreach (var item in items)
Push(item);
}
public T Peek() => Last.Value;
}
Now, create a mapping from Id to children using ToLookup:
var childrenDictionary = persons.Where(p => p.ParentID != 0).ToLookup(p => p.ParentID);
Finally, use the deque to create a working list and add all the root nodes:
var workDeque = new Deque<Person>();
workDeque.EnqueueRange(persons.Where(p => p.ParentID == 0));
Now you can go through the workDeque, adding each root node to the orderedPersons and then pushing the children of the node onto workDeque to be worked next:
var orderedPersons = new List<Person>();
while (workDeque.Count > 0) {
var nextPerson = workDeque.Dequeue();
orderedPersons.Add(nextPerson);
workDeque.PushRange(childrenDictionary[nextPerson.Id]);
}

Multiple same elements in the list, how to display only once, no duplicates?

My List contains multiple subjects, but some of them are the same. How to display all of them but without repetitions?
public class Subject
{
public string SubjectName { get; set; }
public Subject(string subjectName)
{
this.SubjectName = subjectName;
}
}
List<Subject> listOfSubjects = new List<Subject>();
string subject = "";
Console.WriteLine("Enter the name of the subject");
subject = Console.ReadLine();
listofSubjects.Add(new Subject(subject));
string pastSubject = "";
foreach (Subject sub in listOfSubjects)
{
if (sub.SubjectName != pastSubject)
{
Console.WriteLine(sub.SubjectName);
}
pastSubject = sub.SubjectName;
}
One solution can be to build a IEqualityComparer<Subject> for the Subject class. Below is code for it.
public class SubjectComparer : IEqualityComparer<Subject>
{
public bool Equals(Subject x, Subject y)
{
if (x == null && y == null)
{
return true;
}
else if (x == null || y == null)
{
return false;
}
else
{
return x.SubjectName == y.SubjectName;
}
}
public int GetHashCode(Subject obj)
{
return obj.SubjectName.GetHashCode();
}
}
Then just call the System.Linq Distinct function on it supplying the IEqualityComparer instance.
List<Subject> distinctSubjects = listOfSubjects.Distinct(new SubjectComparer()).ToList();
The resultant distinctSubjects list is Distinct.
If you use Linq then you can use GroupBy to get the distinct Subjects by name.
var distinctList = listOfSubjects
.GroupBy(s => s.SubjectName) // group by names
.Select(g => g.First()); // take the first group
foreach (var subject in distinctList)
{
Console.WriteLine(subject.SubjectName);
}
This method returns IEnumerable<Subject>, ie a collection of the actual Subject class.
I created a fiddle.
You can get distinct subject names with
var distinctSubjectNames = listOfSubjects
.Select(s => s.SubjectName)
.Distinct();
foreach (string subjectName in distinctSubjectNames) {
Console.WriteLine(subjectName);
}

How to remove all items from nested list while iterating in C#

I have a nested list that contains
public class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
public List<Person> Childs { get; set; }
}
The list can be used like:
var Persons = new List<Person>();
Persons.Add(new Person("Eric"));
Persons[0].Childs = new List<Person>();
Persons[0].Childs.Add(new Person("Tom"));
Persons[0].Childs.Add(new Person("John"));
Persons[0].Childs[0].Childs = new List<Person>();
Persons[0].Childs[0].Childs.Add(new Person("Bill"));
Persons.Add(new Person("John");
How can I iterate over the list Persons and remove all items with the name "John"? If the name is John the node with the name John and all underlaying subitems should be removed.
You're better off not removing the elements from the existing structure but instead to return a new structure without the "John"'s.
Here's how:
List<Person> Except(List<Person> people, string name) =>
people
.Where(p => p.Name != name)
.Select(p => new Person(p.Name)
{
Childs = Except(p.Childs ?? new List<Person>(), name) // Case typo in method name
})
.ToList();
If you start with your original data:
var Persons = new List<Person>()
{
new Person("Eric")
{
Childs = new List<Person>()
{
new Person("Tom"),
new Person("John")
{
Childs = new List<Person>()
{
new Person("Bill")
}
}
}
},
new Person("John")
};
You can now run this:
List<Person> PersonsNotJohn = Except(Persons, "John");
That gives:
Here is a recursive method that removes all persons with the specified name. You can call it like RemoveAllWithName(Persons, "John");.
private void RemoveAllWithName(List<Person> people, string name)
{
for(int i = people.Count - 1; i >= 0; --i) {
Person person = people[i];
if(person.Name == name) {
people.RemoveAt(i);
} else if(person.Childs != null) {
RemoveAllWithName(person.Childs, name);
}
}
}
Here you go.
Add Namespace
using System.Linq;
Use the below one liner
Persons.RemoveAll(x => x.Name == "John");
static void Main(string[] args)
{
var Persons = new List<Person>();
Persons.Add(new Person("Eric"));
Persons[0].Childs = new List<Person>();
Persons[0].Childs.Add(new Person("Tom"));
Persons[0].Childs.Add(new Person("John"));
Persons[0].Childs[0].Childs = new List<Person>();
Persons[0].Childs[0].Childs.Add(new Person("Bill"));
Persons.Add(new Person("John"));
RemoveAllWithName("John", Persons);
Persons.ForEach(x=>Print(x));
}
private static void RemoveAllWithName(string name, List<Person> persons)
{
if (persons != null && persons.Any())
{
persons.RemoveAll(x => x.Name == name);
}
if (persons != null && persons.Any())
{
persons.ForEach(x => RemoveAllWithName(name, x.Childs));
}
}
private static void Print(Person person)
{
if (person != null)
{
Console.WriteLine(person.Name);
person.Childs?.ForEach(Print);
}
}

LINQ to Get All heirerichal children

I have been digging this quite a while.
public class Person
{
public string Name { get; set; }
public string Age { get; set; }
public List<Person> Children { get; set; }
}
I want a single LINQ query to find out "All the persons whose Age > 4 in this collection".
Note: You have to traverse Collection of Person + Collection of Children, so each children object will have a collection of Person till Children becomes null.
First i can't understand why all your properties private and Age is not int type. So my class looks like this:
public partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
public List<Person> Childrens { get; set; }
}
Note partial word. This word will allow you to place your class logic in separate files and this could be usefull when you creating some custom logic in class.
Then I simply create this method in different file:
public partial class Person
{
public Person GetPersonWithChindren(int maxAge)
{
return new Person
{
Age = this.Age,
Name = this.Name,
Childrens = this.Childrens != null
? this.Childrens
.Where(x => x.Age < maxAge)
.Select(x => x.GetPersonWithChindren(maxAge)) //this line do recursive magic
.ToList()
: null
};
}
}
As you can see this method checking Age of each child and if Age is ok then it checks next level of hierarchy untill Childrens is null.
So you can use it like this:
var person = new Person()
{
//initialisation of your collection here
}
//result will contains only nodes where Person have age < 4 and Childs that have age < 4
var result = person.GetPersonWithChindren(4);
Note that this solution will work normal with linqToEntities. But if you using LinqToSQL this expression produces query to DB on each Person entity. So if you have many Persons and deep hierarhy it will costs you a lot of machine time. In that case you should to write stored procedure with CTE instead of LinQ query.
UPDATE:
You even can write more general solution with a help of Func<T> class like this:
public partial class Person
{
public Person GetPersonWithChindren(Func<Person, bool> func)
{
return new Person
{
Age = this.Age,
Name = this.Name,
Childrens = this.Childrens != null
? this.Childrens
.Where(x => func(x))
.Select(x => x.GetPersonWithChindren(func))
.ToList()
: null
};
}
}
And then you can use it like this:
var result = person.GetPersonWithChindren(x => x.Age < 4);
You always can change your criteria now where you want to use your function.
Create a visitor. In this example by implementing a helper class:
public static class Helpers
public static IEnumerable<Person> GetDescendants(this Person person)
{
foreach (var child in person.Children)
{
yield return child;
foreach (var descendant in child.GetDescendants())
{
yield return descendant;
}
}
}
this is one of the times where the "yield return many" would be useful.
If you're ensuring that .Children is automatically created, then this works:
Func<Person, Func<Person, bool>, Person> clone = null;
clone = (p, f) => f(p) ? new Person()
{
Name = p.Name,
Age = p.Age,
Children = p.Children.Select(c => clone(c, f)).Where(x => x != null).ToList(),
} : null;
var olderThan4 = clone(person, p => p.Age > 4);
Yes, that's it. Effectively three lines.
If you start with this data:
var person = new Person()
{
Name = "Fred", Age = 30,
Children = new List<Person>()
{
new Person() { Name = "Bob", Age = 7, },
new Person() { Name = "Sally", Age = 3, }
},
};
...then you get this result:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
private List<Person> _children = null;
public List<Person> Children
{
get
{
if (_children == null)
{
_children = new List<Person>();
}
return _children;
}
set
{
_children = value;
}
}
}

Remove duplicate objects based on a list properties

I want to distinct a list of objects just based on some properties. These properties are gotten via reflection and some conditions. I searched a lot but cannot found any code snippets or solutions that are able to do a loop in this lambda expression.
List<PropertyInfo> propList = ...
var distinctList = FullList
.GroupBy(uniqueObj =>
{
//do a loop to iterate all elements in propList
})
.Select(x => x.First());
Ok, took me a while to think this one through.
Basically, you can use the Linq GroupBy operator, but you need to use the overload that accepts a custom IEQualityComparer, because you want to verify equality of the objects based on a subset of all their properties.
The subset of properties is stored in a List<PropertyInfo> that you created somewhere else in your code, or that you receive from a service or whatever.
So, implementing IEqualityComparer, then use it with GroupBy:
//Dummy class representing your data.
//
//Notice that I made the IEqualityComparer as a child class only
//for the sake of demonstration
public class DataObject
{
public string Name { get; set; }
public int Age { get; set; }
public int Grade { get; set; }
public static List<PropertyInfo> GetProps()
{
//Only return a subset of the DataObject class properties, simulating your List<PropertyInfo>
return typeof(DataObject).GetProperties().Where(p => p.Name == "Name" || p.Name == "Grade").ToList();
}
public class DataObjectComparer : IEqualityComparer<DataObject>
{
public bool Equals(DataObject x, DataObject y)
{
if (x == null || y == null)
return false;
foreach (PropertyInfo pi in DataObject.GetProps())
{
if (!pi.GetValue(x).Equals(pi.GetValue(y)))
return false;
}
return true;
}
public int GetHashCode(DataObject obj)
{
int hash = 17;
foreach (PropertyInfo pi in DataObject.GetProps())
{
hash = hash * 31 + pi.GetValue(obj).GetHashCode();
}
return hash;
}
}
}
//Then use that in your code:
//
List<DataObject> lst = new List<DataObject>();
lst.Add(new DataObject { Name = "Luc", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 20, Grade = 80 });
List<DataObject> dist = lst.GroupBy(p => p, new DataObject.DataObjectComparer()).Select(g => g.First()).ToList();
//The resulting list now contains distinct objects based on the `Name` and `Grade` properties only.
I hope this helps you get closer to your solution.
Cheers
You can create expression using the property name with this method:
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(arg, propertyName);
//return the property as object
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
And use like this:
var exp = GetPropertySelector<Person>("PropertyName");
Now you can make a distinct easily:
List<Person> distinctPeople = allPeople
.GroupBy(exp.Compile())
.Select(g => g.First())
.ToList();

Categories