Related
Hi I have a class like this:
public class Node
{
public Node();
public long NodeId { get; set; }
public int? LevelId { get; set; }
public ICollection<Node> Children { get; set; }
}
I have managed to recursively add Children to children. Now, printing the data is the problem.
Referencing from this: Recursive List Flattening
this returns a flattened list.
private IEnumerable<Node> GetNodes()
{
// Create a 3-level deep hierarchy of nodes
Node[] nodes = new Node[]
{
new Node
{
NodeId = 1,
LevelId = 1,
Children = new Node[]
{
new Node { NodeId = 2, LevelId = 2, Children = new Node[] {} },
new Node
{
NodeId = 3,
LevelId = 2,
Children = new Node[]
{
new Node { NodeId = 4, LevelId = 3, Children = new Node[] {} },
new Node { NodeId = 5, LevelId = 3, Children = new Node[] {} }
}
}
}
},
new Node { NodeId = 6, LevelId = 1, Children = new Node[] {} }
};
return nodes;
}
However, need data in this format. example.
NodeId | LevelId | ChildNodeId | ChildLeveld | ChildNodeId | ChildLevelId
1 | 1 | 2 | 2
1 | 1 | 3 | 2 | 4 | 3
1 | 1 | 3 | 2 | 5 | 3
like:
currently I have a class with nodeid and levelid. how can I dynamically create other childnodes and return as a collection of objects.
public Class New Class
{
public int NodeId {get;set;}
public int LevelId {get;set;}
public Dictionary<string, object> {get;set;}
// create dynamic childId and levelId in the dictionary
}
You leaf node should be null and not an empty list so you can test for null instead of a count of zero. You also do not need to columns for each level. See code below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
Node root = new Node();
root.setRoot();
root.BuildTable();
root.PrintTable();
Console.ReadLine();
}
}
public class Node
{
DataTable dt = new DataTable();
int maxDepth = 0;
List<Node> nodes { get; set; }
public long NodeId { get; set; }
public int LevelId { get; set; }
public List<Node> Children { get; set; }
public void setRoot()
{
nodes = GetNodes().ToList();
}
public void GetMaxDepth()
{
maxDepth = GetMaxDepthRecursive(nodes, 0);
}
public int GetMaxDepthRecursive(List<Node> nodes, int depth)
{
int returnDepth = depth + 1;
foreach (Node child in nodes)
{
if (child.Children != null)
{
int newDepth = GetMaxDepthRecursive(child.Children, depth + 1);
if (newDepth > returnDepth) returnDepth = newDepth;
}
}
return returnDepth;
}
private IEnumerable<Node> GetNodes()
{
// Create a 3-level deep hierarchy of nodes
Node[] nodes = new Node[]
{
new Node
{
NodeId = 1,
LevelId = 1,
Children = new List<Node>()
{
new Node { NodeId = 2, LevelId = 2, Children = null },
new Node
{
NodeId = 3,
LevelId = 2,
Children = new List<Node>()
{
new Node { NodeId = 4, LevelId = 3, Children = null },
new Node { NodeId = 5, LevelId = 3, Children = null }
}
}
}
},
new Node { NodeId = 6, LevelId = 1, Children = null }
};
return nodes;
}
public void GetTableRowsRecursive(List<Node> nodes, List<KeyValuePair<int,long>> parents)
{
string columnName = "";
foreach (Node node in nodes)
{
if (node.Children == null)
{
DataRow newRow = dt.Rows.Add();
if (parents != null)
{
foreach (KeyValuePair<int, long> parent in parents)
{
columnName = string.Format("Level {0} Node ID", parent.Key.ToString());
newRow[columnName] = parent.Value;
}
}
columnName = string.Format("Level {0} Node ID", node.LevelId);
newRow[columnName] = node.NodeId;
}
else
{
List<KeyValuePair<int, long>> newParents = new List<KeyValuePair<int, long>>();
if(parents != null) newParents.AddRange(parents);
newParents.Add(new KeyValuePair<int,long>(node.LevelId, node.NodeId));
GetTableRowsRecursive(node.Children, newParents);
}
}
}
public void BuildTable()
{
GetMaxDepth();
dt = new DataTable();
for (int i = 1; i <= maxDepth; i++)
{
string columnName = string.Format("Level {0} Node ID", i.ToString());
dt.Columns.Add(columnName, typeof(int));
}
GetTableRowsRecursive(nodes, null);
}
public void PrintTable()
{
string line = string.Join(" ",dt.Columns.Cast<DataColumn>().Select(x => string.Format("{0,-15}",x.ColumnName)));
Console.WriteLine(line);
foreach (DataRow row in dt.AsEnumerable())
{
line = string.Join(" ", row.ItemArray.Select(x => string.Format("{0,-15}", x)));
Console.WriteLine(line);
}
}
}
}
Output
What you're looking for is a recursive traversal of a tree. You track breadcrumbs along the way and build a new path to add to the results whenever you encounter a leaf node (one with no children).
In this output, each row shows ID and Depth separated by a comma, with each node in the path to the leaf node separated by the pipe symbol:
1,1 | 2,2
1,1 | 3,2 | 4,3
1,1 | 3,2 | 5,3
6,1
Press Enter to Quit...
Here is the code that generated that output:
class Program
{
public static void Main()
{
IEnumerable<Node> nodes = GetNodes(); // your test data
List<List<NodeData>> results = new List<List<NodeData>>(); // will store results
foreach (Node n in nodes)
{
n.Traverse(null, results); // start at each root node
}
// output the path to each leaf node from results
// *you can obviously format this differently to your liking
foreach(List<NodeData> path in results)
{
Console.WriteLine(String.Join(" | ", path.Select(p => p.ToString()).ToArray()));
}
Console.WriteLine("Press Enter to Quit...");
Console.ReadLine();
}
private static IEnumerable<Node> GetNodes()
{
// Create a 3-level deep hierarchy of nodes
Node[] nodes = new Node[]
{
new Node
{
NodeId = 1,
LevelId = 1,
Children = new Node[]
{
new Node { NodeId = 2, LevelId = 2, Children = new Node[] {} },
new Node
{
NodeId = 3,
LevelId = 2,
Children = new Node[]
{
new Node { NodeId = 4, LevelId = 3, Children = new Node[] {} },
new Node { NodeId = 5, LevelId = 3, Children = new Node[] {} }
}
}
}
},
new Node
{
NodeId = 6,
LevelId = 1,
Children = new Node[] {}
}
};
return nodes;
}
}
public class Node
{
public Node() {}
public long NodeId { get; set; }
public int? LevelId { get; set; }
public ICollection<Node> Children { get; set; }
public override string ToString()
{
return NodeId.ToString();
}
public void Traverse(List<Node> crumbs, List<List<NodeData>> results)
{
if (crumbs == null) { crumbs = new List<Node>(); }
crumbs.Add(this);
if (Children.Count == 0)
{
List<NodeData> path = new List<NodeData>();
foreach(Node n in crumbs)
{
path.Add(new NodeData() { NodeId = n.NodeId, LevelId = n.LevelId.Value });
}
results.Add(path);
}
else
{
foreach (Node n in Children)
{
n.Traverse(crumbs, results);
}
}
crumbs.Remove(this);
}
}
public class NodeData
{
public long NodeId { get; set; }
public int LevelId { get; set; }
public override string ToString()
{
return NodeId + "," + LevelId;
}
}
For some reason such as performance, I have to use HiarachyId in my database. I have to convert the HierarchyId data type to JSON to show up in FancyTree.
I Use the solution here but won't work. My code was
static void Main(string[] args)
{
{
var dd = new List<Field>();
dd.Add(new Field(1, "Earth", HierarchyId.Parse("/")));
dd.Add(new Field(2, "Europe", HierarchyId.Parse("/1/")));
dd.Add(new Field(3, "South America", HierarchyId.Parse("/2/")));
dd.Add(new Field(4, "Antarctica", HierarchyId.Parse("/3/")));
dd.Add(new Field(5, "Brazil", HierarchyId.Parse("/2/1/")));
dd.Add(new Field(6, "France", HierarchyId.Parse("/1/1/")));
dd.Add(new Field(7, "Germany", HierarchyId.Parse("/1/4/")));
dd.Add(new Field(8, "test", HierarchyId.Parse("/1/5/")));
dd.Add(new Field(9, "McMurdo Station", HierarchyId.Parse("/3/1/")));
dd.Add(new Field(10, "Italy", HierarchyId.Parse("/1/3/")));
dd.Add(new Field(11, "Spain", HierarchyId.Parse("/1/2/")));
dd.Add(new Field(12, "Morano", HierarchyId.Parse("/1/3/1/")));
dd.Add(new Field(13, "Rio de Janeiro", HierarchyId.Parse("/2/1/3/")));
dd.Add(new Field(14, "Paris", HierarchyId.Parse("/1/1/1/")));
dd.Add(new Field(15, "Madrid", HierarchyId.Parse("/1/2/1/")));
dd.Add(new Field(16, "Brasilia", HierarchyId.Parse("/2/1/1/")));
dd.Add(new Field(17, "Bahia", HierarchyId.Parse("/2/1/2/")));
dd.Add(new Field(18, "Salvador", HierarchyId.Parse("/2/1/2/1/")));
dd.Add(new Field(19, "tets1", HierarchyId.Parse("/2/1/3/1/")));
dd.Add(new Field(20, "test2", HierarchyId.Parse("/2/1/3/1/1/")));
dd.Add(new Field(21, "test3", HierarchyId.Parse("/2/1/3/1/1/1/")));
dd.Add(new Field(22, "test24", HierarchyId.Parse("/2/1/3/1/1/2/")));
MyClass clss = new MyClass();
var x= clss.NewMthodTest(dd);
}
}
Method to get child:
public class MyClass
{
public List<HierarchicalNode> NewMthodTest(List<Field> query)
{
var root = new HierarchicalNode("Root", 0);
foreach (var rec in query)
{
var current = root;
foreach (string part in rec.Node.ToString().Split(new[] { '/' },
StringSplitOptions.RemoveEmptyEntries))
{
int parsedPart = int.Parse(part);
current = current.Children[parsedPart - 1];
}
current.Children.Add(new HierarchicalNode(rec.FieldName, rec.Id));
}
return null; // in this method i don't know what do we suppose to return
}
}
and my input parameter class is :
public class Field
{
public Field(long id, string fieldName, HierarchyId node)
{
Id = id;
FieldName = fieldName;
Node = node;
}
public long Id { get; set; }
public string FieldName { get; set; }
public HierarchyId Node { get; set; }
}
and output class is
class HierarchicalNode
{
private readonly List<HierarchicalNode> children =
new List<HierarchicalNode>();
public List<HierarchicalNode> Children { get { return children; } }
private readonly string name;
public string Name { get { return name; } }
private readonly long id;
public long Id { get { return id; } }
public HierarchicalNode(string name, long id)
{
this.name = name;
this.id = id;
}
}
it seems something wrong and it returns this:
One of the benefits of using HierarchyId is so that you can build a tree without doing recursive calls.
I would also name things a big differently. Let's say you call your database table Nodes. Here is the table structure:
CREATE TABLE dbo.Nodes
(
[NodeId] [int] NOT NULL,
[NodeName] [nvarchar](100) NOT NULL,
[HierarchyId] [hierarchyid] NOT NULL,
[Level] AS [HierarchyId].GetLevel() PERSISTED,
CONSTRAINT Primary_Key_Nodes PRIMARY KEY CLUSTERED ([NodeId])
);
CREATE UNIQUE NONCLUSTERED INDEX
[Nodes_1]
ON
[dbo].[Nodes] ([HierarchyId] ASC);
--Important - put level as the first column to index
CREATE UNIQUE NONCLUSTERED INDEX
[Nodes_2]
ON
[dbo].[Nodes] ([Level] ASC, [HierarchyId] ASC);
Here is the SQL to return nodes for a given parent. I would wrap this up in a function called GetDescendantsAndSelf():
SELECT
[NodeId]
,[NodeName]
,[HierarchyId].ToString() AS 'HierarchyPath'
FROM
[dbo].[Nodes]
WHERE
[HierarchyId].IsDescendantOf(#parentHierarchyId) = 1
ORDER BY
[Level] ASC
,[NodeName] ASC;
My data transfer object could look like this:
public class TreeNode
{
public string Text { get; set; } = String.Empty;
public List<TreeNode> Nodes { get; set; } = new List<TreeNode>();
}
GetDescendantsAndSelf() should return a list of Node data access objects like this one:
public class Node
{
public int NodeId { get; set; }
public string NodeName { get; set; } = String.Empty;
public SqlHierarchyId HierarchyId { get; set; }
public int Level => HierarchyId.GetLevel().ToSqlInt32().Value;
}
Here is the code to build a tree:
TreeNode? rootNode = null;
Dictionary<string, TreeNode> treeBuilder = new Dictionary<string, TreeNode>();
string parentHierarchyId = "/1/2/3";
var nodes = GetDescendantsAndSelf(parentHierarchyId);
foreach (Node node in nodes)
{
TreeNode currentNode = new TreeNode() { Text = node.NodeName };
treeBuilder[node.HierarchyId.ToString()] = currentNode;
if (node.Level == 1)
{
rootNode = currentNode;
}
else
{
string parentKey = node.HierarchyId.GetAncestor(1).ToString();
treeBuilder[parentKey].Nodes.Add(currentNode);
}
}
if (rootNode is {})
{
//rootNode contains your tree structure
}
else
{
//no data found for parentHierarchyId
}
After a lot of search On the net with no result, I solve this by myself with a recursive method in c#.
I put my total code here to help people who search in the future for this question. If someone has any nice advice to make it better please leave a note and make me happy.
This is my code:
This is My Main method which calls GetTreeMethod
public List<TreeView> GetTree()
{
//get all nodes from DB to make a collection in RAM
var nodesColect = _getFieldsList.GetFieldsByNode("/");
var x = GetTreeMethod("/", nodesColect);
return x;
}
This is my main recursive method
private List<TreeView> GetTreeMethod(string nodeStr,List<FieldListDto> lstCollection)
{
List<TreeView> lst = new List<TreeView>();
HierarchyId node = HierarchyId.Parse(nodeStr);
var lastItemInCurrentLevel = GetChilds(node, lstCollection);
foreach (var item in lastItemInCurrentLevel)
{
TreeView tr = new TreeView
{
title = item.title,
id = item.id,
node = item.node,
fin = item.fin,
};
tr.children = GetTreeMethod(item.node.ToString(), lstCollection);
lst.Add(tr);
}
return lst;
}
this just gives children of a specific node
private List<TreeView> GetChilds(HierarchyId node, List<FieldListDto> lstCollection)
{
List<TreeView> child = lstCollection.Where(x => x.Node.ToString() != "/" && x.Node.GetAncestor(1).ToString() == node.ToString()).Select(q => new TreeView { id = q.Id, node = q.Node, title = q.FieldName }).ToList();
return child;
}
Models
public class FieldListDto
{
public long id { get; set; }
public string FieldName { get; set; }
public HierarchyId Node { get; set; }
}
public class TreeView
{
public long id { get; set; }
public string title { get; set; }
public HierarchyId node { get; set; }
public List<TreeView> children { get; set; }
}
here my SQL data
and here my final result
I have a table in a database which looks like this:
| id | parentID | name |
|----------+----------+-------------|
|ABCD-12345| | Top |
|----------+----------+-------------|
|ABCD-23456|ABCD-12345| Middle |
|----------+----------+-------------|
|ABCD-34567|ABCD-23456| Bottom |
|----------+----------+-------------|
|ABCD-45678|ABCD-23456| Bottom |
etc. - Basically, a hierarchical structure of N depth. I've taken this and shoved it into a datatable.
I have the following class built to hold this data:
public class TreeNode
{
public string id { get; set; }
public string name { get; set; }
public string parentID { get; set; }
public List<TreeNode> children { get; set; }
}
My goal is to go through each of these DataTable rows and insert them into the appropriate location in the TreeNode structure, but I'm super confused as to how I should approach this.
The main point of confusion for me is how I search through the entire existing structure of TreeNodes to see if a node with the parentID exists. Can anyone point me in the right direction?
I tried the following code, but it doesn't work:
public List<TreeNode> BuildTree(int currNode, List<TreeNode> treeList, DataTable dt)
{
foreach(DataRow row in dt.Rows)
{
if(row[1].ToString() == treeList[currNode].id)
{
treeList[currNode].children.Add(new TreeNode
{
id = row[0].ToString(),
name = row[2].ToString(),
parentID = row[1].ToString()
});
dt.Rows.Remove(row);
if(dt.Rows.Count > 0)
{
currNode++;
BuildTree(currNode, treeList, dt);
}
else
{
return treeList;
}
}
}
return null;
}
The problem is this line:
if(row[1].ToString() == treeList[currNode].id)
which gets an out of range exception, because I have a root at index 0, so on the second run (when currNode is 1), it breaks. I need to traverse to treeList[0].Children[int], followed by treeList[0].Children[int].Children[int] and so on and so forth.
So how do I accomplish this goal?
First I'm going to modify the TreeNode class for our convenience. It's not necessary, but just a nice to have. Also I'm going to assume that in your datatable you've done your error checking and there's only one node with ParentId = "".
public class TreeNode
{
public string Id { get; set; }
public string Name { get; set; }
public string ParentID { get; set; }
public List<TreeNode> Children { get; set; }
public TreeNode()
{
Id = Name = ParentID = string.Empty;
Children = new List<TreeNode>();
}
public bool IsRoot { get { return ParentID == string.Empty; } }
public bool IsChild { get { return Children == null || Children.Count == 0; } }
}
First, I'd convert your datatable data into a list of TreeNode objects. Forget about relationships, just create a list with each objects Children being empty. I wrote a method to simulate data retrival from datatable. Instead of that you can use your actual datatable.
static List<DataTableData> GetDataTableData()
{
var data = new List<DataTableData>
{
new DataTableData() { Id = "23456", ParentID = "12345", Name = "Middle" },
new DataTableData() { Id = "55555", ParentID = "12345", Name = "Middle" },
new DataTableData() { Id = "34567", ParentID = "23456", Name = "Bottom" },
new DataTableData() { Id = "12345", ParentID = string.Empty, Name = "Top" },
new DataTableData() { Id = "45678", ParentID = "23456", Name = "Bottom" },
new DataTableData() { Id = "66666", ParentID = "55555", Name = "Bottom" }
};
return data;
}
And this is what your Main() would look like:
static void Main(string[] args)
{
var treeNodes = new List<TreeNode>();
var dataTable = GetDataTableData();
foreach (var data in dataTable)
{
treeNodes.Add(new TreeNode() { Id = data.Id, Name = data.Name, ParentID = data.ParentID });
}
var root = BuildTree(treeNodes);
Console.ReadLine();
}
Now, in my BuildTree() method, instead of passing the datatable I can pass my TreeNode list, and return just the root node.
public static TreeNode BuildTree(List<TreeNode> nodes)
{
foreach (var node in nodes)
{
node.Children = nodes.Where(x => x.ParentID == node.Id).ToList();
}
return nodes.Find(x => x.IsRoot);
}
BuildTree() Breakdown
The nodes list already have all the nodes corresponding to data in your datatable. The BuildTree() is merely going to create the parent-child relations and fill in each object's Children list.
So I iterate through the list, and see what other elements in the list are supposed to be its children. When you have iterated through the list you'd created all the parent-child relationships. Finally, I pick the root node (the one who's ParentId is empty) and return it.
Edit
Here's an easy method to print and verify your tree.
static void PrintTree(TreeNode node, int indents)
{
for (int tab = 0; tab < indents; tab++)
{
Console.Write("\t");
}
Console.WriteLine("{0} - {1}", node.Id, node.Name);
if (node.Children != null && node.Children.Count > 0)
{
indents++;
foreach (var child in node.Children)
{
PrintTree(child, indents);
}
}
}
My output looks like this:
If you are building a class structure then you need a class with a recursive method. Not sure how efficient this will be if it gets too big. Execute the method from the top of the tree.
public class TreeNode
{
public string id { get; set; }
public string name { get; set; }
public string parentID { get; set; }
public List<TreeNode> children { get; set; }
public TreeNode() {
children = new List<TreeNode>();
}
public TreeNode FindParentWithID(string ID)
{
TreeNode ParentWithID = null;
//check my parentID if i am the one being looked for then return
if (id == ID) return this;
//search children
foreach (TreeNode treeNode in children)
{
ParentWithID = treeNode.FindParentWithID(ID);
if (ParentWithID != null)
{
break;
}
}
return ParentWithID;
}
}
You would load your data into the classes from the database. I had to hard code the values for the example to work:
TreeNode treeNode5 = new TreeNode() { id = "ABCD-12345", parentID = null, name = "Top" };
TreeNode treeNode6 = new TreeNode() { id = "ABCD-12346", parentID = "ABCD-12345", name = "Middle" };
treeNode5.children.Add(treeNode6);
TreeNode treeNode7 = new TreeNode() { id = "ABCD-12347", parentID = "ABCD-12346", name = "Bottom" };
TreeNode treeNode8 = new TreeNode() { id = "ABCD-12348", parentID = "ABCD-12346", name = "Bottom" };
treeNode6.children.Add(treeNode7);
treeNode6.children.Add(treeNode8);
TreeNode topOne = treeNode5.FindParentWithID("ABCD-12346");
topOne will be end up being treeNode6 name="Middle" in this example.
try this code, i have same issue and it works perfectly
this method used to build tree from list of items, by looping through all items, and add each item to its parent's child list. and return only the root item with its nested child.
public TreeNode BuildTree(List<TreeNode> source)
{
// build the children list for each item
foreach (var item in source)
{
var itm = source.Where(i => i.parentID == item.Id).ToList();
item.ChildItems = itm;
}
// return only the root parents with its child inside
return source.Where(i => i.parentID == null).FirstOrDefault();
}
noting that this method return only on TreeNode Object with its child, you can return List by changing .FirstOrDefault() to .ToList() in return line
Tag class consists of ID Name and List<Tagging> :
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tagging> Tagging { get; set; }
}
Tagging class :
public class Tagging
{
public int Id { get; set; }
[ForeignKey("ParentTag")]
public int ParentId { get; set; }
public Tag ParentTag { get; set; }
[ForeignKey("ChildTag")]
public int ChildId { get; set; }
public Tag ChildTag { get; set; }
}
Tagging class just express many to many relationship between tags, for hierarchical purpose.
For example given a list :
List<Tag> tags = new List<Tag>();
var parent = new Tag {Name = "Parent", Id = 1, Tagging = new List<Tagging>{ new Tagging{ ParentId = 1, ChildId = 2}}};
var child = new Tag {Name = "Child", Id = 2, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 3 }}};
var grandChild = new Tag {Name = "GrandChild", Id = 3};
tags.Add(parent);
tags.Add(child);
tags.Add(grandChild);
I am trying to loop through all hierarchical objects connected to his parent. For example if you call a method getAllHiearchyObject(Tag parent)
Output should be something like this :
Name : "Parent", Id = 1;
Name : "Child", Id : 2;
Name : "GrandChild", Id :3
I need an actual implementation of getAllHiearchyObject(Tag parent)
How about this...
static IEnumerable<Tag> FlattenTag(Tag root)
{
yield return root;
if (root.Tagging != null)
foreach (var childTagging in root.Tagging)
if (childTagging.ChildTag != null)
foreach (var grandChildTag in FlattenTag(childTagging.ChildTag))
yield return grandChildTag;
}
Note that the second foreach above allows for the use of yield with recursion.
Usage...
foreach(var tag in FlattenTag(root))
...
Only one parent to one child.
For a simple case when you have only one parent-child relationship you can create methods like:
public static class EnumerableExtensions
{
#region Methods
public static IEnumerable<T> Unwind<T>(T first, Func<T, T> getNext)
where T : class
{
if (getNext == null)
throw new ArgumentNullException(nameof(getNext));
return Unwind(
first: first,
getNext: getNext,
isAfterLast: item =>
item == null);
}
public static IEnumerable<T> Unwind<T>(
T first,
Func<T, T> getNext,
Func<T, Boolean> isAfterLast)
{
if (getNext == null)
throw new ArgumentNullException(nameof(getNext));
if (isAfterLast == null)
throw new ArgumentNullException(nameof(isAfterLast));
var current = first;
while(!isAfterLast(current))
{
yield return current;
current = getNext(current);
}
}
#endregion
}
And use them in the following way (I have set ChildTag in Taggings, as it will be done by EF):
List<Tag> tags = new List<Tag>();
var grandChild = new Tag { Name = "GrandChild", Id = 3 };
var child = new Tag { Name = "Child", Id = 2, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 3, ChildTag = grandChild } } };
var parent = new Tag { Name = "Parent", Id = 1, Tagging = new List<Tagging> { new Tagging { ParentId = 1, ChildId = 2, ChildTag = child } } };
tags.Add(parent);
tags.Add(child);
tags.Add(grandChild);
var fromParent = EnumerableExtensions
.Unwind(
parent,
item =>
item?.Tagging?.FirstOrDefault()?.ChildTag)
.ToArray();
Console.WriteLine("Parent to child:");
foreach (var item in fromParent)
{
Console.WriteLine(item);
}
Proper parent to many children
For a proper tree creation you will have to use:
public class UnwoundItem<T> : IEnumerable<UnwoundItem<T>>
{
private readonly T _item;
private readonly IEnumerable<UnwoundItem<T>> _unwoundItems;
public UnwoundItem(T item, IEnumerable<UnwoundItem<T>> unwoundSubItems)
{
this._item = item;
this._unwoundItems = unwoundSubItems ?? Enumerable.Empty<UnwoundItem<T>>();
}
public T Item
{
get
{
return this._item;
}
}
public IEnumerable<UnwoundItem<T>> UnwoundSubItems
{
get
{
return this._unwoundItems;
}
}
public IEnumerator<UnwoundItem<T>> GetEnumerator()
{
return this._unwoundItems.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
and
public static class EnumerableExtensions
{
#region Methods
public static UnwoundItem<T> UnwindMany<T>(
T first,
Func<T, IEnumerable<T>> getNext)
where T : class
{
if (getNext == null)
throw new ArgumentNullException(nameof(getNext));
return UnwindMany(
first: first,
getNext: getNext,
isAfterLast: collection =>
collection == null);
}
public static UnwoundItem<T> UnwindMany<T>(
T first,
Func<T, IEnumerable<T>> getNext,
Func<IEnumerable<T>, Boolean> isAfterLast)
{
if (getNext == null)
throw new ArgumentNullException(nameof(getNext));
if (isAfterLast == null)
throw new ArgumentNullException(nameof(isAfterLast));
var currentItems = getNext(first);
if (isAfterLast(currentItems))
return new UnwoundItem<T>(
item: first,
unwoundSubItems: Enumerable.Empty<UnwoundItem<T>>());
return new UnwoundItem<T>(
item: first,
unwoundSubItems: currentItems
.Select(item =>
UnwindMany(
item,
getNext,
isAfterLast)));
}
#endregion
}
It can be tested with:
private static void Print<T>(IEnumerable<UnwoundItem<T>> items, Func<T, String> toString, Int32 level)
{
var indent = new String(' ', level * 4);
foreach (var item in items)
{
Console.Write(indent);
Console.WriteLine(toString(item.Item));
Print(item.UnwoundSubItems, toString, level + 1);
}
}
...
var grandChild = new Tag { Name = "GrandChild", Id = 3 };
var grandChild2 = new Tag { Name = "GrandChild 2", Id = 33 };
var child = new Tag { Name = "Child", Id = 2, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 3, ChildTag = grandChild } } };
var child2 = new Tag { Name = "Child 2", Id = 22, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 33, ChildTag = grandChild2 } } };
var parent = new Tag { Name = "Parent", Id = 1,
Tagging = new List<Tagging> {
new Tagging { ParentId = 1, ChildId = 2, ChildTag = child },
new Tagging { ParentId = 1, ChildId = 2, ChildTag = child2 } }
};
var fromParent = EnumerableExtensions
.UnwindMany(
parent,
item =>
item?.Tagging?.Select(tagging => tagging.ChildTag));
Console.WriteLine("Parent to child:");
Print(new[] { fromParent }, item => item.Name, 0);
I've got a text file that looks like this:
{ Id = 1, ParentId = 0, Position = 0, Title = "root" }
{ Id = 2, ParentId = 1, Position = 0, Title = "child 1" }
{ Id = 3, ParentId = 1, Position = 1, Title = "child 2" }
{ Id = 4, ParentId = 1, Position = 2, Title = "child 3" }
{ Id = 5, ParentId = 4, Position = 0, Title = "grandchild 1" }
I'm looking for a generic C# algorithm that will create an object hierarchy from this. A "Hierarchize" function, if you will, that turns this data into an object hierarchy.
Any ideas?
edit I've already parsed the file into .NET objects:
class Node
{
public int Id { get; }
public int ParentId { get; }
public int Position { get; }
public string Title { get; }
}
Now I need to actually arrange the objects into an object graph.
Many thanks to Jon and to mquander - you guys gave me enough information to help me solve this in a proper, generic way. Here's my solution, a single generic extension method that converts objects into hierarchy form:
public static IEnumerable<Node<T>> Hierarchize<T, TKey, TOrderKey>(
this IEnumerable<T> elements,
TKey topMostKey,
Func<T, TKey> keySelector,
Func<T, TKey> parentKeySelector,
Func<T, TOrderKey> orderingKeySelector)
{
var families = elements.ToLookup(parentKeySelector);
var childrenFetcher = default(Func<TKey, IEnumerable<Node<T>>>);
childrenFetcher = parentId => families[parentId]
.OrderBy(orderingKeySelector)
.Select(x => new Node<T>(x, childrenFetcher(keySelector(x))));
return childrenFetcher(topMostKey);
}
Utilizes this small node class:
public class Node<T>
{
public T Value { get; private set; }
public IList<Node<T>> Children { get; private set; }
public Node(T value, IEnumerable<Node<T>> children)
{
this.Value = value;
this.Children = new List<Node<T>>(children);
}
}
It's generic enough to work for a variety of problems, including my text file issue. Nifty!
****UPDATE****: Here's how you'd use it:
// Given some example data:
var items = new[]
{
new Foo()
{
Id = 1,
ParentId = -1, // Indicates no parent
Position = 0
},
new Foo()
{
Id = 2,
ParentId = 1,
Position = 0
},
new Foo()
{
Id = 3,
ParentId = 1,
Position = 1
}
};
// Turn it into a hierarchy!
// We'll get back a list of Node<T> containing the root nodes.
// Each node will have a list of child nodes.
var hierarchy = items.Hierarchize(
-1, // The "root level" key. We're using -1 to indicate root level.
f => f.Id, // The ID property on your object
f => f.ParentId, // The property on your object that points to its parent
f => f.Position, // The property on your object that specifies the order within its parent
);
Hmm... I don't quite see how that works. How can 2 and 5 both have parent=1, position=0? Should 5 have parent 2, 3 or 4?
Okay, this new version goes through the all the nodes three times:
Load all nodes and put them into the a map
Associate each node with its parent
Sort each node's child by position
It's not well-encapsulated, nicely error checking etc - but it works.
using System;
using System.Collections.Generic;
using System.IO;
public class Node
{
private static readonly char[] Braces = "{}".ToCharArray();
private static readonly char[] StringTrim = "\" ".ToCharArray();
public Node Parent { get; set; }
public int ParentId { get; private set; }
public int Id { get; private set; }
public string Title { get; private set; }
public int Position { get; private set; }
private readonly List<Node> children = new List<Node>();
public List<Node> Children { get { return children; } }
public static Node FromLine(string line)
{
Node node = new Node();
line = line.Trim(Braces);
string[] bits = line.Split(',');
foreach (string bit in bits)
{
string[] keyValue = bit.Split('=');
string key = keyValue[0].Trim();
string value = keyValue[1].Trim();
switch (key)
{
case "Id":
node.Id = int.Parse(value);
break;
case "ParentId":
node.ParentId = int.Parse(value);
break;
case "Position":
node.Position = int.Parse(value);
break;
case "Title":
node.Title = value.Trim(StringTrim);
break;
default:
throw new ArgumentException("Bad line: " + line);
}
}
return node;
}
public void Dump()
{
int depth = 0;
Node node = this;
while (node.Parent != null)
{
depth++;
node = node.Parent;
}
Console.WriteLine(new string(' ', depth * 2) + Title);
foreach (Node child in Children)
{
child.Dump();
}
}
}
class Test
{
static void Main(string[] args)
{
var dictionary = new Dictionary<int, Node>();
using (TextReader reader = File.OpenText("test.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Node node = Node.FromLine(line);
dictionary[node.Id] = node;
}
}
foreach (Node node in dictionary.Values)
{
if (node.ParentId != 0)
{
node.Parent = dictionary[node.ParentId];
node.Parent.Children.Add(node);
}
}
foreach (Node node in dictionary.Values)
{
node.Children.Sort((n1, n2) =>
n1.Position.CompareTo(n2.Position));
}
Node root = dictionary[1];
root.Dump();
}
}
Sample text file:
{ Id = 5, ParentId = 4, Position = 0, Title = "grandchild 1" }
{ Id = 2, ParentId = 1, Position = 0, Title = "child 1" }
{ Id = 4, ParentId = 1, Position = 2, Title = "child 3" }
{ Id = 3, ParentId = 1, Position = 1, Title = "child 2" }
{ Id = 1, ParentId = 0, Position = 0, Title = "root" }
Output:
root
child 1
child 2
child 3
grandchild 1
Once you have the file parsed in you can follow this blog on how to assemble the objects into a hierarchy using LINQ.
I assume that your example incorrectly gives the wrong parent ID to object #5. This should cover it. Caveats: Assumes the "topmost" node always has a parent ID of zero. Ignores any nodes that aren't eventually descended from the topmost node. Behavior will be odd if presented with duplicate IDs.
public class FlatObj
{
public int Id;
public int ParentId;
public int Position;
public string Title;
}
public class Node
{
public int ID;
public string Title;
public IList<Node> Children;
public Node(FlatObject baseObject, IList<Node> children)
{
this.ID = baseObject.Id;
this.Title = baseObject.Title;
this.Children = children;
}
}
public static Node CreateHierarchy(IEnumerable<FlatObject> objects)
{
var families = objects.ToLookup(x => x.ParentId);
var topmost = families[0].Single();
Func<int, IList<Node>> Children = null;
Children = (parentID) => families[parentID]
.OrderBy(x => x.Position)
.Select(x => new Node(x, Children(x.Id))).ToList();
return new Node(topmost, Children(topmost.Id));
}
public static void Test()
{
List<FlatObj> objects = new List<FlatObj> {
new FlatObj { Id = 1, ParentId = 0, Position = 0, Title = "root" },
new FlatObj { Id = 2, ParentId = 1, Position = 0, Title = "child 1" },
new FlatObj { Id = 3, ParentId = 1, Position = 1, Title = "child 2" },
new FlatObj { Id = 4, ParentId = 1, Position = 2, Title = "child 3" },
new FlatObj { Id = 5, ParentId = 2, Position = 0, Title = "grandchild" }};
var nodes = CreateHierarchy(objects);
}
class Node {
public int Id { get;set; }
public int ParentId { get;set; }
public int Position { get;set; }
public string Title { get;set; }
public IEnumerable<Node> Children { get; set; }
public override string ToString() { return ToString(0); }
public string ToString(int depth) {
return "\n" + new string(' ', depth * 2) + Title + (
Children.Count() == 0 ? "" :
string.Join("", Children
.Select(node => node.ToString(depth + 1))
.ToArray()
);
}
}
class Program {
static void Main(string[] args) {
var data = new[] {
new Node{ Id = 1, ParentId = 0, Position = 0, Title = "root" },
new Node{ Id = 2, ParentId = 1, Position = 0, Title = "child 1" },
new Node{ Id = 3, ParentId = 1, Position = 1, Title = "child 2" },
new Node{ Id = 4, ParentId = 1, Position = 2, Title = "child 3" },
new Node{ Id = 5, ParentId = 3, Position = 0, Title = "grandchild 1" }
};
Func<Node, Node> transform = null;
transform = node => new Node {
Title = node.Title,
Id = node.Id,
ParentId = node.ParentId,
Position = node.Position,
Children = (
from child in data
where child.ParentId == node.Id
orderby child.Position
select transform(child))
};
Console.WriteLine(transform(data[0]));
}
}
result:
root
child 1
child 2
grandchild 1
child 3
Are you certain the last line's ParentID is 1? The title says grandchild, but it would be a child of "root" if i'm reading things correctly.
Here is the example that #baran asked for:
var lHierarchicalMenuItems = lMenuItemsFromDB.Hierarchize(0, aItem => aItem.Id, aItem => aItem.ParentId, aItem => aItem.Position);