Building a tree out of items list - c#

I would like to get some suggestions on how I can build a tree out of items list in a efficient way
public class Item
{
public Item(int id, int? parentId)
{
Id = id;
ParentId = parentId;
}
public int Id { get; private set; }
public int? ParentId { get; private set; }
public List<Item> SubItems { get; set; }
}
private Item BuildATree()
{
var items = new List<Item>()
{
new Item(1, null),
new Item(2, 1),
new Item(3, 1),
new Item(4, 1),
new Item(5, 2),
new Item(6, 2),
new Item(7, 4),
new Item(8, 7),
new Item(9, 1),
};
//Build a tree out of list items
}
The result I am expecting is each item being in its parent's SubItems list
Not necessarily using the same Item class, because Ids would be redundant then

I'd use LINQ:
//Build a tree out of list items
foreach (Item item in items)
{
item.SubItems = items.Where(i => i.ParentId.Value == item.Id).ToList();
}
UPD:
To simplify moving items from one parent to another you'll need to store a reference to parent Item in every Item. Something like:
public class Item
{
public Item(int id, int? parentId)
{
Id = id;
ParentId = parentId;
}
public int Id { get; private set; }
public int? ParentId { get; private set; }
public List<Item> SubItems { get; set; }
private Item _parent;
public Item Parent
{
get { return _parent; }
set
{
if (_parent != null)
_parent.SubItems.Remove(this);
_parent = value;
if (_parent != null)
_parent.SubItems.Add(this);
}
}
}
If you implement in that way then just setting new Parent item via this property will be enough to modify both old and new parent's SubItems collections - but beware that you'll also need a bit more complex list initialization mechanism.

Solution which is efficient enough
private void RecursiveBuilder(ref Item i, IEnumerable<Item> li)
{
var item = i;
i.SubItems = (from n in li where n.ParentId == item.Id select n).ToList();
i.SubItems.ForEach(f => RecursiveBuilder(ref f, li));
}

If you don't want/have Link :
Dictionary<int,Item> dic = new Dictionary<int,Item>();
foreach(Item item in items)
{
Item parent;
if(item.ParentId!=null && dic.TryGetValue(item.ParentId, out parent))
parent.SubItems.Add(item);
else
dic.Add(item.Id, item);
}
Item root = dic[1];
I supposed that there will always be a Item with id = 1 and that's the root of the tree.
If you want to use new Class without the ids, create them instead of simply add them to their parents.

Related

C# Creating Binary Tree Treeview from folders and files

I am trying to create my own treeview binary tree where it shows folders and children / parents for certain folders.
I have a big list of folders then I have it in a custom object the Index, Parent Name, Children bool, and display name on the tree.
I can create the tree but when it creates it all the children are under the first node and not under the correct ones.
I see that treenode has level and parent if I could set those then this would be easy but I see they are readonly so I’m kinda stuck so far…
List<TreeNodeEnd> lstTreeNod = new List<TreeNodeEnd>();
Dictionary<int, TreeNode> valuePairs = new Dictionary<int, TreeNode>();
foreach (var node in lstTreeNod)
{
TreeNodeCollection items;
TreeNode treeNode;
if (node.TreeIndex == 0)
{
//node.DisplayName = "PDM Vault";
//TreeNode root2 = new TreeNode("PDM Vault", 3, 3, items);
//root.Name = "PDM Vault";
items = treeView1.Nodes;
//items = items.Add(node.DisplayName);
//treeView1.Nodes
}
else //in this ELSE DETERMINE ABOUT FATHER & CHILDREN EQUALS AND NOT NULL
{ // ValuePairs = Final Tree ?
//items = valuePairs[node.TreeIndex - 1].Nodes;
items = valuePairs[node.TreeIndex].Nodes;
TreeNode treeNode = items.Add(node.DisplayName);
}
TreeNode treeNode = items.Add(node.DisplayName);
if (valuePairs.ContainsKey(node.TreeIndex))
{
if (node.HasChildren == true)
valuePairs[node.TreeIndex] = treeNode;
}
else
{
valuePairs.Add(node.TreeIndex, treeNode);
}
}
public class TreeNodeEnd
{
public string DisplayName { get; set; }
public int TreeIndex { get; set; }
public bool HasChildren { get; set; }
public string Father { get; set; }
}

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

Multilevel parent child relation sorting using Linq c#

I have a list and I need to sort it to this hierarchy
{ Id=1, ParentId = null, Name = "Item1", Type="0"}
{ Id=2, ParentId = 1, Name = "ItemChild1", Type="1"}
{ Id=3, ParentId = 1, Name = "ItemChild2", Type="1"}
{ Id=4, ParentId = 3, Name = "ItemGrandChild1", Type="2"}
{ Id=5, **ParentId = 1**, Name = "ItemGrandChild2", Type="2"}
{ Id=6, ParentId = null, Name = "Item7", Type="0"}
...
Unlike normal parent child relationships, here
Items of Type2 can be child of either Type1 or Type0
All the Id's are guids
I have seen may stack answers on child parent sorting using Linq. But my case is different.
Any elegant way using Linq ?
If I were you, I would give up trying to sort this with either linq or sql. Abstractions can be helpful, but in a complex case like this they will just get in your way.
You will save a lot of time if you just write your own sorter.
class MyCompare : IComparer<MyObject>
{
public int Compare(x1, x2)
{
if (x1.parent == parent1 && x2.parent == parent2)
return 1;
if (x1.parent == x2.parent)
return 0;
//ect
}
}
List<MyObject> list = GetWeirdObjects();
list.Sort(new MyCompare());
Create a lookup to find fast the children and project them to the parent collection. As far as i see it does not depend at all if your children have different types as long as they know which element is their parent.
public class TreeItem
{
public int Id { get; set; }
public int? ParentId { get; set; }
public IEnumerable<TreeItem> Children { get; set; }
public void PrintAllChildren()
{
this.PrintAllChildren(0);
}
private void PrintAllChildren(int indent)
{
Debug.WriteLine("{1}Item id: {0}", this.Id, string.Concat(Enumerable.Repeat<int>(0, indent).Select(i => " ")));
if (this.Children != null)
foreach (var item in this.Children)
item.PrintAllChildren(indent + 1);
}
}
public static class TreeItemExtension
{
public static IEnumerable<TreeItem> GetAsTree(this IEnumerable<TreeItem> data)
{
var lookup = data.ToLookup(i => i.ParentId);
return lookup[null].Select(i => {
i.FillChildren(lookup);
return i;
});
}
private static TreeItem FillChildren(this TreeItem item, ILookup<int?, TreeItem> lookup)
{
item.Children = lookup[item.Id].Select(i => i.FillChildren(lookup));
return item;
}
}

Custom Sorting in .NET

I have designed a Class for Parent Child relationship
class Category
{
public string CatName;
public string CatId;
public IList<Category> childCategory = new List<Category>();
public void addChildCat(Category childCat)
{
this.childCategory.Add(childCat);
}
public Category SortedCategory(Category cat)
{
// Should return the sorted cat i.e topmost parent
}
}
Here by Parent will not have Catname or CatId, it will have Child Categories which has CatName, CatId as well as another Child Category List and it goes till "N" categories
Here I need to get the Top Parent with all the child categories sorted by CatName. Any ideas How this can be achieved?
Is my class design GOOD?
You can't because you have not a reference to the parent. You have to add a field:
public Category Parent { get; set; }
and modify the add method to set the parent:
public void addChildCat(Category childCat)
{
childCat.Parent = this;
this.childCategory.Add(childCat);
}
You need the parent to get the root:
public static Category SortedCategory(Category cat)
{
// get the root
var root = cat;
while(root.Parent != null) root = root.Parent;
return root.GetSorted();
}
private Category GetSorted()
{
var sortedChildren = new List<Category>(childCategories).ConvertAll(c => c.GetSorted());
sortedChildren.Sort((c1, c2) => c1.CatName.CompareTo(c2.Catname));
return new Category { CatName = root.CatName,
Catid = root.CatId,
childCategories = sortedChildren; }
}
You can use the SortedList to keep track of the child categories instead.
If I understand this, we have a tree structure right? And what is the result you are expecting, the sorted children of the topmost parent (root)?
Instead of :
public string CatName;
public string CatId;
I would do:
class Cat
{
public string Name { get; set; }
public string Id { get; set; }
}
And instead of:
public Category SortedCategory(Category cat)
{
// Should return the sorted cat i.e topmost parent
}
I would do:
var category = new List<Cat>
{
new Cat() {Name = "cat1", Id = "123"},
new Cat() {Name = "cat2", Id = "124"},
new Cat() {Name = "cat3", Id = "125"},
new Cat() {Name = "cat4", Id = "126"}
};
category.Sort(( cat1, cat2) => ((Convert.ToInt32(cat1.Id) > Convert.ToInt32(cat2.Id)) ? 1 : 0) );

C# - Recursive Grouping

I have a List of objects of type IGroup. These can be nested to an umlimited level, and I'm trying to group them after retrieving them from a database. I can't get my head around how to recursively add all groups to the right parents. Any groups with null as a parent are top level groups. I can't guarantee the order they come out of the database.
public interface IGroup {
string ID { get; set; }
string Name { get; set; }
string ParentID { get; set; }
IList<IGroup> Groups { get; set; }
...
So if I had a list of:
Group1: ID = g1, ParentID = null
Group1a: ID = g2, ParentID = g1
Group2: ID = g3, ParentID = null
Group1b: ID = g4, ParentID = g3
Group1bc: ID = g5, ParentID = g4
I'm trying to group them as:
|Group1
|--Group1a
|--Group1b
|--|
|--Group1bc
|Group2
Anyone fancy a stab at grouping them recursively?
No need to be recursive. To wit:
var lookup = items.ToDictionary(g => g.ID); // items is IEnumerable<IGroup>
foreach (var item in items.Where(g => g.ParentID != null)) {
lookup[item.ParentID].Groups.Add(item);
}
var parents = items.Where(g => g.ParentID == null);
Note that lookup[item.ParentID] will throw if there is no IGroup with the corresponding ParentID. You can handle this more gracefully with TryGetValue.
My implementation of IGroup:
public class Group : IGroup {
public string ID { get; set; }
public string Name { get; set; }
public string ParentID { get; set; }
public IList<IGroup> Groups { get; set; }
public Group() {
Groups = new List<IGroup>();
}
}
My test items:
IEnumerable<IGroup> items = new List<IGroup>() {
new Group() { ID = "g1", ParentID = null },
new Group() { ID = "g2", ParentID = "g1" },
new Group() { ID = "g3", ParentID = null },
new Group() { ID = "g4", ParentID = "g3" },
new Group() { ID = "g5", ParentID = "g4" },
new Group() { ID = "g6", ParentID = "g5" }
};
This is not recursive, but here's a solution (assuming you have all you groups in a list called groups)
var rootGroups = new List<IGroup>();
var dic = groups.ToDictionary(g => g.ID);
foreach (var g in groups)
{
if (g.ParentID == null)
{
rootGroups.Add(g);
}
else
{
IGroup parent;
if (dic.TryGetValue(g.ParentID, out parent))
{
parent.Groups.Add(g);
}
}
}
You could try ordering them by parent id, assuming that the parent group is always created before the child group.
Group by ParentID (Linq: GroupBy), order by ID.
Start with an empty root node (ID: null) and add all items with this ParentID. Recursively continue this process for any item that has been added.
As you extract each element from the database, you need to add it to its parent. So keep a Dictionary to help find the parent. If you get a child before its parent, then you can insert a placeholder until you get the real thing.
void BuildGroups()
{
foreach( IGroup x /* obtained from database or a collection or wherever */ )
AddGroup( x );
}
Dictionary<string,IGroup> _groups = new Dictionary<string,IGroup>;
string _parentGroupName = "PARENT";
void AddGroup( IGroup x )
{
// locate (or create) parent and add incoming group
IGroup parent;
string parentID = x.ParentID ?? _parentGroupName;
if( !groups.TryGetValue( parentID, out parent ) )
{
parent = new Group( parentID ); // SEE NOTE BELOW!
_groups[parentID] = parent;
}
parent.Groups.Add( x );
// locate (or insert) child, and make sure it's up to date
IGroup child;
if( groups.TryGetValue( x.ID, out child ) )
{
// We must have inserted this ID before, because we found one
// of ITS children first. If there are any other values besides
// ParentID and ID, then copy them from X to child here.
}
else
{
// first time we've seen this child ID -- insert it
_groups[x.ID] = x;
}
}
The dictionary element at _parentGroupName will then be a dummy node whose children are all of your top-level groups (i.e. the ones with NULL as ParentID from the database); from that element you can do a recursive traversal:
VisitGroups( _groups[_parentGroupName], "" );
void VisitGroups( string ID, string indent )
{
IGroup x;
if( _groups.TryGetValue( ID, out x ) )
{
foreach( IGroup y in x.Groups )
{
WriteLine( indent + " {0}", y.ID );
VisitGroups( y.ID, indent + " " );
}
}
}
NOTE: This implementation runs in a single inline pass through the data -- you can add elements immediately as they're retrieved from the database, and you only need to make a single pass through the data. That means you save some time and some memory. But in return, it requires that you be able to allocate an object with type IGroup() to act as a placeholder, in case a child is retrieved before its parent. You can only avoid that requirement if you know something about the order of the objects or if you process your dictionary in two passes, as is shown in the other answers.
I use a sentinel value, _parentGroupName, to keep the top-level nodes in the same collection as all the others. You can easily alter this to use a separate collection for the top level nodes instead if you prefer.
You could try this
public interface IGroup
{
string ID { get; set; }
string Name { get; set; }
string ParentID { get; set; }
List<IGroup> Groups { get; set; }
}
public class Group : IGroup
{
public string ID { get; set; }
public string Name { get; set; }
public string ParentID { get; set; }
public List<IGroup> Groups { get; set; }
public Group()
{
}
public Group(string id, string name, List<IGroup> childs)
{
ID = id;
Name = name;
Groups = (List<IGroup>)childs.Cast<IGroup>();
}
}
class Program
{
static void Main(string[] args)
{
List<IGroup> OriginalList;
List<IGroup> HirarchList = new List<IGroup>();
OriginalList = new List<IGroup>()
{
new Group() { ID = "g1", ParentID = null },
new Group() { ID = "g2", ParentID = "g1" },
new Group() { ID = "g3", ParentID = null },
new Group() { ID = "g4", ParentID = "g3" },
new Group() { ID = "g5", ParentID = "g4" },
new Group() { ID = "g6", ParentID = "g5" } };
HirarchList = GetCreateList(null, OriginalList);
}
public static List<IGroup> GetCreateList(string id, List<IGroup> list)
{
List<IGroup> temp = new List<IGroup>();
temp = (from item in list
where item.ParentID == id
select (IGroup)new Group(item.ID, item.Name,GetCreateList(item.ID, list))).ToList();
return (List<IGroup>)temp;
}
}

Categories