Efficient ordering of hierarchy data with only parentId - c#

Problem
What is the fastest method to order a flat unordered set of nodes such that the parent is always listed before it's children. My current solution uses a queue to iterate the tree in a breadth first manner. However I've been wondering if there was a better / more efficient method.
Notes:
I can't pre compute any values
Id and ParentId could also be Guids (even if ints I can't guarantee sequential ids)
Linq Pad Code
void Main()
{
var nodes = new Node[] {
new Node { Id = 7, ParentId = 3 },
new Node { Id = 4, ParentId = 2 },
new Node { Id = 1, ParentId = 0 },
new Node { Id = 2, ParentId = 1 },
new Node { Id = 3, ParentId = 1 },
new Node { Id = 5, ParentId = 2 },
new Node { Id = 6, ParentId = 3 },
new Node { Id = 8, ParentId = 0 },
};
SortHierarchy(nodes).Dump();
}
IEnumerable<Node> SortHierarchy(IEnumerable<Node> list)
{
// hashtable lookup that allows us to grab references to the parent containers, based on id
var lookup = new Dictionary<int, List<Node>>();
Queue<Node> nodeSet = new Queue<Node>();
List<Node> children;
foreach (Node item in list) {
if (item.ParentId == 0) { // has no parent
nodeSet.Enqueue(item); // This will add all root nodes in the forest
} else {
if (lookup.TryGetValue(item.ParentId, out children)) {
// add to the parent's child list
children.Add(item);
} else {
// no parent added yet
lookup.Add(item.ParentId, new List<Node> { item });
}
}
}
while (nodeSet.Any()) {
var node = nodeSet.Dequeue();
if (lookup.TryGetValue(node.Id, out children)) {
foreach (var child in children) {
nodeSet.Enqueue(child);
}
}
yield return node;
}
}
private class Node {
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}
Research
I did find this however it wasn't quite what I was after (also the code doesn't work)
Building hierarchy objects from flat list of parent/child

This code gives the same dump() result, order the list with parentid first:
IEnumerable<Node> SortHierarchy2(IEnumerable<Node> list)
{
return list.OrderBy(l => l.ParentId);
}
This will work as long as your child ids are < their parents ids.

Related

List of descendant ID - children, grandchildren etc

CREATE TABLE [dbo].[Topic] (
[Id] SMALLINT NOT NULL,
[ParentId] SMALLINT NULL
);
I have a simple table (above) with a parent/child hierarchy. I'm using Entity Framework to extract the data. The number of rows is less than 100.
I want to get a list of descendant ID, consisting of the children, grandchildren and so on (possibly with the option of including the original root parent). As the table only contains a small number of rows, it's probably easier to extract all the data to a List<Topic> first and work on that, but I stand to be corrected.
The preferred output would be: Dictionary<int, List<int>>
Where the key would be the ID and the list would contain child/grandchild ID's
I've looked at tens and tens of solutions online but I can't find a solution that meets my needs. Can anyone help?
You could populate a dictionary with the ParentId->Id relations and use that to resolve sub-topics:
// prepare dictionary
var dictionary = new Dictionary<short, List<Topic>>();
// in real life this would get replaced by your database query
var topics = new List<Topic>
{
new Topic { Id = 1 },
new Topic { Id = 2, ParentId = 1 },
new Topic { Id = 3, ParentId = 1 },
new Topic { Id = 4, ParentId = 1 },
new Topic { Id = 5, ParentId = 1 },
new Topic { Id = 6, ParentId = 2 },
new Topic { Id = 7, ParentId = 2 },
new Topic { Id = 8, ParentId = 3 },
new Topic { Id = 9, ParentId = 4 },
new Topic { Id = 10, ParentId = 4 },
new Topic { Id = 11, ParentId = 8 },
new Topic { Id = 12, ParentId = 8 }
};
// populate dictionary with relations from DB
foreach(var topic in topics)
{
var key = topic.ParentId ?? -1;
if(!dictionary.ContainsKey(key))
{
dictionary.Add(key, new List<Topic>());
}
dictionary[key].Add(topic);
}
Now that you have the mappings available, you can write a simple recursive iterator method to resolve the descendants of a given id:
IEnumerable<short> GetDescendantIDs(short from)
{
if(dictionary.ContainsKey(from))
{
foreach(var topic in dictionary[from])
{
yield return topic.Id;
foreach(var child in GetDescendants(topic.Id))
yield return child;
}
}
}
// resolves to [1, 2, 6, 7, 3, 8, 11, 12, 4, 9, 10, 5]
var descendantsOfRoot = GetDescendantIDs(-1);
// resolves to [8, 11, 12]
var descendantsOfThree = GetDescendantIDs(3);
The Topic class used in the example above is just:
class Topic
{
internal short Id { get; set; }
internal short? ParentId { get; set; }
}
Consider that result has to be stored in tree:
public class TopicModel
{
public int Id { get; set; }
public int? ParentId{ get; set; }
public List<TopicModel> Children { get; set; };
}
Building tree:
// retrieveing from database
var plainResult = context.Topic
.Select(t => new TopicModel
{
Id = x.Id
ParentId = x.ParentId
})
.ToList();
var lookup = plainResult.Where(x => x.ParentId != null).ToLookup(x => x.ParentId);
foreach (c in plainResult)
{
if (lookup.Contains(c.Id))
{
c.Children = lookup[c.Id].ToList();
}
}
// here we have all root items with children intialized
var rootItems = plainResult.Where(x => x.ParentId == null).ToList();
And searching for children:
public static IEnumerable<TopicModel> GetAllChildren(TopicModel model)
{
if (model.Children != null)
{
foreach (var c in model.Children)
{
yield return c;
foreach (sc in GetAllChildren(c))
yield return sc;
}
}
}

Creating a recursive list of objects C#

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

How would I search through all nested children of a custom class?

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

Create a nested list of items from objects with a parent reference

I am using C# and I have a list of items that have a nullable parent ID property.
I need to convert this to a list of items that have a list of their children and keep going down generations until there are no items left.
My existing class
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
}
My first thoughts...
Create a class
public class ItemWithChildren
{
public Item Item { get; set; }
public List<ItemWithChildren> Children { get; set; }
}
Now I need some way to get a List<ItemWithChildren> that has all the top level Item objects and their children into the Children property.
Note that the nesting is not a set number of levels.
I was hoping there was an elegant LINQ query that would work. So far I just have this...
var itemsWithChildren = items.Select(a => new ItemWithChildren{ Item = a });
It is more readable to not use pure Linq for this task, but a mixture of Linq and looping.
Given the following container:
class Node
{
public int Id { get; set; }
public int? ParentId { get; set; }
public List<Node> Children { get; set; }
}
Then you can make the tree with the following code.
var nodes = new List<Node>
{
new Node{ Id = 1 },
new Node{ Id = 2 },
new Node{ Id = 3, ParentId = 1 },
new Node{ Id = 4, ParentId = 1 },
new Node{ Id = 5, ParentId = 3 }
};
foreach (var item in nodes)
{
item.Children = nodes.Where(x => x.ParentId.HasValue && x.ParentId == item.Id).ToList();
}
var tree = nodes.Where(x => !x.ParentId.HasValue).ToList();
This will handle any level of depth and return a proper tree.
Given the following method to print the tree:
private void PrintTree(IEnumerable<Node> nodes, int indent = 0)
{
foreach(var root in nodes)
{
Console.WriteLine(string.Format("{0}{1}", new String('-', indent), root.Id));
PrintTree(root.Children, indent + 1);
}
}
The output of this call is:
1
-3
--5
-4
2
If however you want to use pure Linq for this, you can do the following, however to me it is harder to read:
var tree = nodes.Select(item =>
{
item.Children = nodes.Where(child => child.ParentId.HasValue && child.ParentId == item.Id).ToList();
return item;
})
.Where(item => !item.ParentId.HasValue)
.ToList();
This might help ?
var itemsWithChildren = items.Select(a => new ItemWithChildren{
Item = a,
Children = items.Where(b => b.ParentId==a.Id)
.ToList()
});
Then update model to achieve it as
public string Name { get; set; }
[DisplayName("Parent Category")]
public virtual Guid? CategoryUID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
I think you need to do it in two steps...
I've tested and it definitely works...
var items = new List<Item>();
items.Add(new Item { Id = 1, ParentId = null });
items.Add(new Item { Id = 2, ParentId = 1 });
items.Add(new Item { Id = 3, ParentId = 1 });
items.Add(new Item { Id = 4, ParentId = 3 });
items.Add(new Item { Id = 5, ParentId = 3 });
var itemsWithChildren = items.Select(a =>
new ItemWithChildren { Item = a }).ToList();
itemsWithChildren.ForEach(a =>
a.Children = itemsWithChildren.Where(b =>
b.Item.ParentId == a.Item.Id).ToList());
var root = itemsWithChildren.Single(a => !a.Item.ParentId.HasValue);
Console.WriteLine(root.Item.Id);
Console.WriteLine(root.Children.Count);
Console.WriteLine(root.Children[0].Children.Count);
Console.WriteLine(root.Children[1].Children.Count);
Output...
1
2
0
2

C# algorithm for generating hierarchy

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

Categories

Resources