How to build recursive menu with unique ID in C#? - c#

I need to build a menu tree with id like below, I tried below code for first build a tree but doesn't return the expected output.
Can anyone help how to do this?
Below is just an example of how the items could get arranged with id
<ul>
<li id = "1">Office</li>
<li id = "2">Home
<ul>
<li id = "2-1">Bed</li>
<li id = "2-2">Pillow</li>
</ul>
</li>
<li id = "3">School
<ul>
<li id = "3-1">Class
<ul>
<li id = "3-1-1">Grade</li>
</ul>
</li>
</ul>
</li>
</ul>
List<Node> _nodes = new List<Node>();
_nodes.Add(new Node() { id = 1, parentid = 0, name = "Office" });
_nodes.Add(new Node() { id = 2, parentid = 0, name = "Home" });
_nodes.Add(new Node() { id = 3, parentid = 2, name = "Bed" });
_nodes.Add(new Node() { id = 4, parentid = 2, name = "Pillow" });
_nodes.Add(new Node() { id = 6, parentid = 0, name = "School" });
_nodes.Add(new Node() { id = 7, parentid = 6, name = "Class" });
_nodes.Add(new Node() { id = 8, parentid = 7, name = "Grade" });
private static string RenderMenu(List<Node> nodes)
{
StringBuilder menu = new StringBuilder();
if (nodes.Count > 0)
{
menu.AppendLine("<ul>");
foreach (Node _n in nodes)
{
menu.AppendLine("<li>");
if (_n.parentid != 0)
menu.AppendLine(RenderMenu(nodes.Where(p => p.parentid == _n.parentid).ToList()));
else
menu.AppendLine(_n.name);
menu.AppendLine("</li>");
}
menu.AppendLine("</ul>");
}
return menu.ToString();
}

string menu = OrganizeMenu(_nodes);
private static string OrganizeMenu(List<Node> nodes)
{
menu.Append("<ul>"); // start the List
List<Node> parentNode = nodes.Where(item => item.parentid == 0).ToList(); // Get all Parent Node (Root Node i.e. a Node with parentid = 0)
List<Node> childNode = nodes.Except(parentNode).ToList(); // Get all Child Node (i.e. a Node with parentid != 0)
foreach (var pNode in parentNode) // traverse for each Parent Node and add this to root level
{
menu.Append("<li>");
menu.Append(pNode.name);
GetChilds(nodes, pNode);
menu.Append("</li>");
}
menu.Append("</ul>"); // end the list
return menu.ToString();
}
private static void GetChilds(List<Node> nodes, Node parentNode)
{
List<Node> childs = nodes.Where(item=> item.parentid == parentNode.id).ToList();
foreach (var child in childs)
{
menu.Append("<ul>");
menu.Append(child.name);
GetChilds(nodes, child);
menu.Append("</ul>");
}
}

You have a tree structure but you draw it as a plain structure.
I mean your foreach (Node _n in nodes) will make as many <li> as you have items in your List<Node>. It is not what you want.
It would be more logical and convenient to pass id instead of List<Node> as you need the whole List at every moment of time since you don't know which items will be presented in the next sub-menu.
Now about unique ID. I would recomment using the same ids as you have in your List<Node>. It is easier and it is guaranteed that they are unique (otherwise, we simply cannot build a menu). You need to store the full path to it.
There is a good option - to pass an array \ List with ids as function argument. However, global Stack<int> with Node ids is a great option.
Every time you are "entering" a node - you add it to stack, every time you "leave" it - you remove one item. At every moment, you have the actual recursive path.
That's how I would solve this problem:
public static List<Node> _nodes;
public static Stack<int> _currentPath;
public static string RenderMenu(int id)
{
var currentChildNodes = _nodes.Where(x => x.parentid == id).ToArray();
if (currentChildNodes == 0) return String.Empty; // No children - no submenu!
StringBuilder menu = new StringBuilder();
menu.AppendLine("<ul>");
foreach (var currentChildNode in currentChildNodes)
{
_currentPath.Push(currentChildNode.id);
menu.AppendLine(String.Format("<li id='{0}'>{1}{2}</li>",
String.Join('-', Array.Reverse(_currentPath.ToArray())),
currentChildNode.name,
RenderMenu(currentChildNode.id));
_currentPath.Pop();
// Keep in mind that if currentChildNode does not have child nodes
// then RenderMenu() will return String.Empty and result will be "<li>name</li>"
}
menu.AppendLine("</ul>");
return menu.ToString();
}
// Somewhere in code
RenderMenu(0);
If, for some reason, you need to generate a consequent int id, you can change the loop this way:
for (int i = 0; i < currentChildNodes.Count; i++) // Convert to for-loop
{
var currentChildNode = currentChildNodes[i];
_currentPath.Push(i + 1); // Change node.id to i
menu.AppendLine(String.Format("<li id='{0}'>{1}{2}</li>",
String.Join('-', _currentPath.ToArray()),
currentChildNode.name,
RenderMenu(currentChildNode.id));
_currentPath.Pop();
// Keep in mind that if currentChildNode does not have child nodes
// then RenderMenu() will return String.Empty and result will be "<li>name</li>"
}

Related

Creating a tree from a collection List<T>

In order to create a tree, I use the following code.
var db = _context.GetContext();
var accounts = await db
.Set<TradingAccount>()
.ToListAsync(cancellationToken: token);
accounts.ForEach(
account => account.Children = accounts.Where(
child => child.ParentTradingAccountId == account.Id).ToList()
);
return accounts;
It works well (albeit not fast), but it does not create a completely correct tree. The same element can be both root and dependent. How can I exclude elements from the selection that have already been included in the tree?
The problem is that the code above adds dependent nodes as children, but does not remove them from the top-level list. Ususally recursion can be used to create tree structures, like so:
private IEnumerable<TracingAccount> GetAccounts(IEnumerable<TradingAccount> allAccounts, int parentTrackingAccountId)
{
var accounts = allAccounts
.Where(x => x.ParentTrackingAccountId == parentTrackingAccountId)
.ToList();
foreach (var acc in accounts)
{
// Get children of current node
acc.Children = GetAccounts(allAccounts, acc.Id);
}
return accounts;
}
Above function retrieves all accounts for a specified parent id and calls itself again (that's why it is called a recursive function) to retrieve the children.
You can use the function in your code as follows (I assume that the root level accounts have a parent id of 0):
var db = _context.GetContext();
var allAccounts = await db
.Set<TradingAccount>()
.ToListAsync(cancellationToken: token);
var accounts = GetAccounts(allAccounts, 0);
return accounts;
The call to GetAccounts gets all root level accounts and - because the function calls itself again for each account - by that also retrieves the subtree of the root level accounts.
I wrote an algoritm to build a tree from a flat list. (a faster approach than filtering the same list over and over) As the items comes from a database, the parentId's should exists, and circular references should not occur. This example isn't able to handle those. But it might give you a jump-start how to make a faster algoritm.
In a nutshell, loop until all items are added. Use a while-loop instead of a foreach. The problem with foreach is that you aren't allowed to make modifications to the collection while iterating. This can be solved by creating copies, but it will end-up in many copy actions.
When it is a child (so the parentId is filled), I use the lookup dictionary to check if his parent was already added. If not, I'll skip it and check the next item. (this makes it possible that the parent is below the child in de list).
When it is added to the parent, also add the child to the lookup, so their children are able to add them as child.
When it is a parent, I add it to the rootNodes, and add it to the lookup.
Multiple rootnodes are supported.
public static class TreeBuilder
{
public static IEnumerable<Node> BuildTree(IEnumerable<Item> items)
{
var nodeLookup = new Dictionary<int, Node>();
var rootNodes = new List<Node>();
var itemCopy = items.ToList(); // we don't want to modify the original collection, make one working copy.
int index = 0;
while (true)
{
// when the item copy is empty, we're done.
if (itemCopy.Count == 0)
return rootNodes.ToArray();
// do go out of bounds.
index = index % itemCopy.Count;
// get the current item on that index.
var current = itemCopy[index];
// does it have a parent?
if(current.ParentId.HasValue)
{
// yes, so, it's a child
// look if the parent is already found in the lookup.
if (nodeLookup.TryGetValue(current.ParentId.Value, out var parentNode))
{
// create a new node
var node = new Node { Id = current.Id };
// add it to the lookup
nodeLookup.Add(current.Id, node);
// add it as child node to the parent.
parentNode.ChildNodes.Add(node);
// remove it from the itemCopy (so don't check it again)
itemCopy.RemoveAt(index);
// The index doesn't need to be increase, because the current items is removed.
}
else
// next item, the parent is not in the tree yet.
index++;
}
else
{
// root node
var node = new Node { Id = current.Id };
nodeLookup.Add(current.Id, node);
rootNodes.Add(node);
itemCopy.RemoveAt(index);
}
}
}
}
My test setup:
private void button4_Click(object sender, EventArgs e)
{
/*
* 1
* |
* +- 2
* |
* +- 3
* |
* +- 5
* |
* +- 4
*/
var items = new[]
{
new Item{ Id = 1, ParentId = null},
new Item{ Id = 2, ParentId = 1},
new Item{ Id = 3, ParentId = 2},
new Item{ Id = 4, ParentId = 5},
new Item{ Id = 5, ParentId = 2},
};
var tree = TreeBuilder.BuildTree(items);
DisplayTree(tree);
}
private void DisplayTree(IEnumerable<Node> nodes, string indent = "")
{
foreach (var node in nodes)
{
Trace.WriteLine($"{indent}{node.Id}");
DisplayTree(node.ChildNodes, indent + " ");
}
}
The classes I used are:
public class Node
{
public int Id { get; set; }
public List<Node> ChildNodes { get; } = new List<Node>();
}
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
}
Which results in:
1
2
3
5
4

What can I do to improve the following code so that the result is a single `TreeView.AddRange()`

Just doing some final optimizations on my custom TreeView control, and it looks like I can optimize the load method a bit further. Here is the segment I am focused on which I feel can be optimized by building all the TreeNodes first, and then using a single TreeView.AddRange( nodes[] ) method.
// Create hierarchy and load into view
foreach (var id in _treeNodes.Keys)
{
var node = GetNode(id);
var obj = (T)node.Tag;
var parentId = getParentId(obj);
if (parentId.HasValue)
{
var parentNode = GetNode(parentId.Value);
if(parentNode == null)
{
Invoke((MethodInvoker)(() => Nodes.Add(node)));
} else
{
Invoke((MethodInvoker)(() => parentNode.Nodes.Add(node)));
}
}
else
{
Invoke((MethodInvoker)(() => Nodes.Add(node)));
}
}
_treeNodes is Dictionary<ulong, TreeNode>
GetNode() is :
if(_treeNodes.ContainsKey(id))
{
return _treeNodes[id];
} else
{
return null;
}
getParentId() is a delegate Func<NtfsUsnJournal.UsnEntry, ulong?> getParentId = (x => x.ParentFileReferenceNumber)
The code currently gets all the values from a dictionary list which is all the directories in the NTFS Journal, which happens very fast. The delay is that on each Node and the children nodes, it is being added to the TreeView directly.
What I would like to do, is build a list of root Nodes (containing all child nodes of course), and then pass that as an array of TreeNode[] to the .AddRange( TreeNode[] ) method in a single Invoke().
How can this code be modified to accomplish this ?
Figured it out, answer was staring me in the face :
List<TreeNode> _t = new List<TreeNode>();
foreach (var id in _treeNodes.Keys)
{
var node = GetNode(id);
var obj = (T)node.Tag;
var parentId = getParentId(obj);
if (parentId.HasValue)
{
var parentNode = GetNode(parentId.Value);
if(parentNode == null)
{
_t.Add(node);
} else
{
parentNode.Nodes.Add(node);
}
}
else
{
_t.Add(node);
}
}
Invoke((MethodInvoker)(() => Nodes.AddRange(_t.ToArray())));

Text Delta Determination (Hierarchical)

I have a case which requires comparison (and diff determination) of two texts.
These are actually hierarchical configuration files and all sub items are indented in every level. Example :
File 1 :
conf_1
conf_1_1
conf_1_2
conf_1_3
conf_1_3_1
conf_1_4
conf_1_4_1
conf_1_4_2
File 2 :
conf_1
conf_1_1
conf_1_2
conf_1_3
conf_1_3_1
conf_1_3_2
conf_1_4
conf_1_4_1
conf_1_4_2
conf_1_5
The comparison between these two files should result as :
Result :
conf_1
conf_1_3
conf_1_3_2
conf_1_5
Remarks :
I'm only interested in plus delta (the additions in second file).
Order of lines may change between two files, this shouldn't be
interpreted as difference, as soon as hierarchy is preserved.
I have a solution :
"Flattening" the lines of each files ( e.g. conf_1 > conf_1_3 > conf_1_3_1 ), performing a brute-force comparison (comparing each line in File1 with each line in File2) and then re-indenting the different lines.
But I'm looking for more efficient solutions.
Any idea?
Thanks in advance.
I would suggest populating a 2 hierarchical lists and processing them recursively.
Start with defining a simple class:
class Node
{
public string Text;
public List<Node> Children;
}
Here the Text is supposed to contain the text with indentation removed.
Then you would populate two node lists from your files, build another node list with differences, and write the result to another file. Something like this:
var nodes1 = ReadNodes(sourceFile1);
var nodes2 = ReadNodes(sourceFile2);
var diff = GetDiff(nodes1, nodes2);
if (diff.Count > 0)
{
using (var sw = new StreamWriter(diffFile))
WriteDiff(sw, diff);
}
The used methods are:
static List<Node> ReadNodes(string fileName)
{
// I'm leaving that part for you
}
static List<Node> GetDiff(List<Node> nodes1, List<Node> nodes2)
{
if (nodes2 == null || nodes2.Count == 0) return null;
if (nodes1 == null || nodes1.Count == 0) return nodes2;
var map = nodes1.ToDictionary(n => n.Text);
var diff = new List<Node>();
foreach (var n2 in nodes2)
{
Node n1;
if (!map.TryGetValue(n2.Text, out n1))
diff.Add(n2);
else
{
var childDiff = GetDiff(n1.Children, n2.Children);
if (childDiff != null && childDiff.Count > 0)
diff.Add(new Node { Text = n2.Text, Children = childDiff });
}
}
return diff;
}
static void WriteDiff(TextWriter output, List<Node> nodes, int indent = 0)
{
if (nodes == null) return;
foreach (var node in nodes)
{
for (int i = 0; i < indent; i++)
output.Write(' ');
output.WriteLine(node.Text);
WriteDiff(output, node.Children, indent + 4);
}
}
Test using your example:
var nodes1 = new List<Node>
{
new Node { Text = "conf_1", Children = new List<Node> {
new Node { Text = "conf_1_1" },
new Node { Text = "conf_1_2" },
new Node { Text = "conf_1_3", Children = new List<Node> {
new Node { Text = "conf_1_3_1" },
} },
new Node { Text = "conf_1_4", Children = new List<Node> {
new Node { Text = "conf_1_4_1" },
new Node { Text = "conf_1_4_2" },
} },
}},
};
var nodes2 = new List<Node>
{
new Node { Text = "conf_1", Children = new List<Node> {
new Node { Text = "conf_1_1" },
new Node { Text = "conf_1_2" },
new Node { Text = "conf_1_3", Children = new List<Node> {
new Node { Text = "conf_1_3_1" },
new Node { Text = "conf_1_3_2" },
} },
new Node { Text = "conf_1_4", Children = new List<Node> {
new Node { Text = "conf_1_4_1" },
new Node { Text = "conf_1_4_2" },
} },
new Node { Text = "conf_1_5" },
}},
};
var diff = GetDiff(nodes1, nodes2);
if (diff.Count > 0)
{
using (var sw = new StringWriter())
{
WriteDiff(sw, diff);
Console.WriteLine(sw.ToString());
}
}

Finding difference between two tree structures C#

I am in need of a function that will compare the difference between two different file structures and return that difference as a file structure.
I have a class, "Element" that has properties ID and Children, with ID being a string and Children being a collection of Element.
public class Element
{
string ID { get; }
IEnumerable<Element> Children { get; }
}
Now, let's say I have the following structures of Elements:
Structure A Structure B
- Category 1 - Category 1
- Child X - Child X
- Child Y - Child Z
- Category 2
I would like to return a structure that tells me which elements are present in structure A but missing from structure B, which would look as follows:
Structure Diff
- Category 1
- Child Y
- Category 2
Is there a simple way of doing this using LINQ, or a straight-forward algorithm (Assuming there can be many levels to the tree).
Yes, it is. You can just compare two enumerables of strings that contains paths of files:
Category 1\
Category 1\Child X
Category 1\Child Y
Category 2\
Category 1\
Category 1\Child X
Category 1\Child Z
Having these two enumerables you can call Enumerable.Except method to keep items from the first enumerable that are missing in the second enumerable.
Sample implementation to get you started (tested only on one case):
internal class Program {
private static void Main(string[] args) {
var c1 = new Element[] {
new Element() {ID = "Category 1", Children = new Element[] {
new Element() {ID = "Child X" },
new Element() {ID = "Child Y" }
}},
new Element() {ID = "Category 2",}
};
var c2 = new Element[] {
new Element() {ID = "Category 1", Children = new Element[] {
new Element() {ID = "Child X" },
new Element() {ID = "Child Z" }
}},
};
var keys = new HashSet<string>(GetFlatKeys(c2));
var result = FindDiff(c1, keys).ToArray();
Console.WriteLine(result);
}
private static IEnumerable<Element> FindDiff(Element[] source, HashSet<string> keys, string key = null) {
if (source == null)
yield break;
foreach (var parent in source) {
key += "|" + parent.ID;
parent.Children = FindDiff(parent.Children, keys, key).ToArray();
if (!keys.Contains(key) || (parent.Children != null && parent.Children.Length > 0)) {
yield return parent;
}
}
}
private static IEnumerable<string> GetFlatKeys(IEnumerable<Element> source, string key = null) {
if (source == null)
yield break;
foreach (var parent in source) {
key += "|" + parent.ID;
yield return key;
foreach (var c in GetFlatKeys(parent.Children, key))
yield return c;
}
}
}
As said in another answer, it's easier to first get flat list of keys for each element in second tree, then filter out elements from the first tree based on that list.

How to create objects with retrieved Hierarchical result set?

I am using C# language. My problem is that i don't know how to store my retrieved hierarchical result set to my object.
Here's is my Object:
public class CategoryItem
{
public string Name { get; set; }
public int CategoryID { get; set; }
public int ParentID { get; set; }
public List<CategoryItem> SubCategory = new List<CategoryItem>();
public List<CategoryItem> GetSubCategory()
{
return SubCategory;
}
public void AddSubCategory(CategoryItem ci)
{
SubCategory.Add(ci);
}
public void RemoveSubCategory(CategoryItem ci)
{
for (int i = 0; i < SubCategory.Count; i++)
{
if (SubCategory.ElementAt(i).CategoryID == ci.CategoryID)
{
SubCategory.RemoveAt(i);
break;
}
}
}
}
Here's is my sample retrieve data set from MSSQL server
ID PrntID Title
_______ _______
1 0 Node1
2 1 Node2
3 1 Node3
4 2 Node4
5 2 Node5
6 2 Node6
7 3 Node7
8 4 Node8
9 4 Node9
10 9 Node10
Tree view for easy reference
Node 1
-Node 2
--Node 4
---Node 8
---Node 9
----Node 10
--Node 5
--Node 6
-Node 3
--Node 7
My problem is how to do I store this result to my "CategoryItem Object". I don't have any clue do I need to use iteration for this? Specially when the node is 2 level-deep.
I want to store it in such a like this:
List<CategoryItem> items = new List<CategoryItem>();
with this I can dig every objects in the 'items' object and I can access its sub-category / child / children using the GetSubCategory() method of my class. Is this possible?
If you know that in your DataSet a node will never appear before its parent, you can use this code. Here you keep track of the already read items in a Dictionary when you can look for parents of the newly read nodes. If you find the parent you add the new item to its children, otherwise it's a first level node.
public static List<CategoryItem> LoadFromDataSet(DataSet aDS)
{
List<CategoryItem> result = new List<CategoryItem>();
Dictionary<int, CategoryItem> alreadyRead = new Dictionary<int, CategoryItem>();
foreach (DataRow aRow in aDS.Tables["YourTable"].Rows)
{
CategoryItem newItem = new CategoryItem();
newItem.CategoryID = (int)aRow["ID"];
newItem.ParentID = (int)aRow["PrntID"];
newItem.Name = (string)aRow["Title"];
alreadyRead[newItem.CategoryID] = newItem;
CategoryItem aParent;
if (alreadyRead.TryGetValue(newItem.ParentID, out aParent))
aParent.AddSubCategory(newItem);
else
result.Add(newItem);
}
return result;
}
If my assumption isn't true (i.e. it is possible for a node to appear in the DataSet before its parent), you have to first read all the nodes (and put them in the Dictionary), then loop through the same Dictionary to build the result. Something like this:
public static List<CategoryItem> LoadFromDataSet(DataSet aDS)
{
List<CategoryItem> result = new List<CategoryItem>();
Dictionary<int, CategoryItem> alreadyRead = new Dictionary<int, CategoryItem>();
foreach (DataRow aRow in aDS.Tables["YourTable"].Rows)
{
CategoryItem newItem = new CategoryItem();
newItem.CategoryID = (int)aRow["ID"];
newItem.ParentID = (int)aRow["PrntID"];
newItem.Name = (string)aRow["Title"];
alreadyRead[newItem.CategoryID] = newItem;
}
foreach (CategoryItem newItem in alreadyRead.Values)
{
CategoryItem aParent;
if (alreadyRead.TryGetValue(newItem.ParentID, out aParent))
aParent.AddSubCategory(newItem);
else
result.Add(newItem);
}
return result;
}
You have to write recursive code to achieve this.
//First of all, find the root level parent
int baseParent = "0";
// Find the lowest root parent value
foreach (var selection in collection)
{
//assign any random parent id, if not assigned before
if (string.IsNullOrEmpty(baseParent))
baseParent = selection["PrntID"];
//check whether it is the minimum value
if (Convert.ToInt32(selection["PrntID"]) < Convert.ToInt32(baseParent))
baseParent = selection["PrntID"];
}
//If you are sure that your parent root level node would always be zero, then you could //probably skip the above part.
//Now start building your hierarchy
foreach (var selection in collection)
{
CategoryItem item = new CategoryItem();
//start from root
if(selection["Id"] == baseParentId)
{
//add item property
item.Id = selection["id];
//go recursive to bring all children
//get all children
GetAllChildren(item , collection);
}
}
private void GetAllChildren(CategoryItem parent, List<Rows> Collection)
{
foreach(var selection in Collection)
{
//find all children of that parent
if(selection["PrntID"] = parent.Id)
{
CategoryItem child = new CategoryItem ();
//set properties
child.Id = selection["Id"];
//add the child to the parent
parent.AddSubCategory(child);
//go recursive and find all child for this node now
GetAllChildren(child, Collection);
}
}
}
Note: This is not exactly working code. But this would give you insight how you go around and build a Hierarchical data structure that has to be represented as object.
Load your table in to a Datatable and fist find the root node and create root object
DataRow[] rootRow = table.Select("PrntID = 0");
CategoryItem root = new CategoryItem() { CategoryID = (int)rootRow[0]["ID"].ToString(), Name = rootRow[0]["Title"].ToString(), ParentID = (int)rootRow[0]["PrntID"].ToString() };
Then you need to call recursive method to add sub categories,
GetCategoryItem((int)rootRow[0]["ID"].ToString(), root);
change below method as you wish.
public void GetCategoryItem(CategoryItem parant)
{
DataRow[] rootRow = table.Select("PrntID =" + parant.CategoryID);
for (int i = 0; i < rootRow.Length; i++)
{
CategoryItem child = new CategoryItem() { CategoryID = (int)rootRow[i]["ID"].ToString(), Name = rootRow[i]["Title"].ToString(), ParentID = (int)rootRow[i]["PrntID"].ToString() };
GetCategoryItem(child);
parant.SubCategory.Add(child);
}
}

Categories