C# linq hierarchy tree - c#

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

Related

How Do I Deserialize Into Local Collections in .NET?

I am trying to build a data pipeline in .NET. I have been given an xsd and have used the XML Schema Definition Tool to generate C# classes that represent the object model. In order to load this data into my data store I need to transform the data coming out of the XML into structure that matches my application schema and collect/dedupe elements. To do this I have a parser class that will read a file and load the contents into local collections which will then be loaded into my database. I see two options to do this -
Manually loop through the XML with an XmlReader and pull out the data I need, loading it into the local collections in stream. This is not desirable because it does not take advantage of the strongly typed/strict xsd that I was given and requires a lot of hard coding things like while (reader.Read()), check for specific XML nodes, and then `reader.GetAttribute("HardCodedString").
Use XmlSerializer to deserialize the whole file at once and then loop through the deserialized collections and insert into my local collections. This is not desirable because the files could be very large and this method forces me to loop through all of the data twice (once to deserialize and once to extract the data to my local collections).
Ideally I would like some way to register a delegate to be executed as each object is deserialized to insert into my local collections. Is there something in the framework that allows me to do this? Requirements are as follows:
Performant - Only loop through the data once.
Functional - Data is inserted into the local collections during deserialization.
Maintainable - Utilize strongly typed classes that were generated via the xsd.
I have created a minimal example to illustrate my point.
Example XML File:
<Hierarchy xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.example.com/example">
<Children>
<Child ChildId="1" ChildName="First">
<Parents>
<Parent ParentId="1" ParentName="First" RelationshipStart="1900-01-01T00:00:00"/>
<Parent ParentId="2" ParentName="Second" RelationshipStart="2000-01-01T00:00:00"/>
</Parents>
</Child>
<Child ChildId="2" ChildName="Second">
<Parents>
<Parent ParentId="2" ParentName="Second" RelationshipStart="1900-01-01T00:00:00"/>
<Parent ParentId="3" ParentName="Third" RelationshipStart="2000-01-01T00:00:00"/>
</Parents>
</Child>
</Children>
</Hierarchy>
Local collections I am trying to load:
public Dictionary<int, string> Parents { get; }
public Dictionary<int, string> Children { get; }
public List<Relationship> Relationships { get; }
Manual version (not maintainable and doesn't use xsd):
public void ParseFileManually(string fileName)
{
using (var reader = XmlReader.Create(fileName))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Hierarchy")
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Child")
{
int childId = int.Parse(reader.GetAttribute("ChildId"));
string childName = reader.GetAttribute("ChildName");
Children[childId] = childName;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Parent")
{
int parentId = int.Parse(reader.GetAttribute("ParentId"));
string parentName = reader.GetAttribute("ParentName");
DateTime relationshipStart = DateTime.Parse(reader.GetAttribute("RelationshipStart"));
Parents[parentId] = parentName;
Relationships.Add(
new Relationship{
ParentId = parentId,
ChildId = childId,
Start = relationshipStart
});
}
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Child")
{
break;
}
}
}
}
}
}
}
}
Deserialize version (loops through the data twice):
public void ParseFileWithDeserialize(string fileName)
{
var serializer = new XmlSerializer(typeof(Hierarchy));
using (var fileStream = new FileStream(fileName, FileMode.Open))
{
var fileData = (Hierarchy) serializer.Deserialize(fileStream);
foreach (var child in fileData.Children)
{
Children[child.ChildId] = child.ChildName;
foreach (var parent in child.Parents)
{
Parents[parent.ParentId] = parent.ParentName;
Relationships.Add(
new Relationship
{
ParentId = parent.ParentId,
ChildId = child.ChildId,
Start = parent.RelationshipStart
});
}
}
}
}
You should use some annotations to get the data from the correct field in the XML, if you use these definitions;
public class Hierarchy
{
public Hierarchy()
{
Children = new List<Child>();
}
public List<Child> Children { get; set; }
}
public class Child
{
public Child()
{
Parents = new List<Parent>();
}
[XmlAttribute("ChildId")]
public int ChildId { get; set; }
[XmlAttribute("ChildName")]
public string ChildName { get; set; }
public List<Parent> Parents { get; set; }
}
public class Parent
{
[XmlAttribute("ParentId")]
public int ParentId { get; set; }
[XmlAttribute("ParentName")]
public string ParentName { get; set; }
[XmlAttribute("RelationshipStart")]
public DateTime RelationshipStart { get; set; }
}
Then you should be able to simplify your code to;
public static Hierarchy Deserialize(string fileName)
{
using (var fileStream = new StreamReader(fileName, Encoding.UTF8))
{
XmlSerializer ser = new XmlSerializer(typeof(Hierarchy));
return (Hierarchy)ser.Deserialize(fileStream);
}
}
To test it out you can create a sample data set and serialize it to a file, then use the above code to read it back
public static void Serialize(Hierarchy h, string fileName)
{
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(Hierarchy));
StreamWriter sw = new StreamWriter(fileName, false, Encoding.UTF8);
ser.Serialize(sw, h);
}
Test Code
static void Test()
{
Hierarchy h = new Hierarchy();
Parent p1 = new Parent() { ParentId = 1, ParentName = "First", RelationshipStart = DateTime.Now };
Parent p2 = new Parent() { ParentId = 2, ParentName = "Second", RelationshipStart = DateTime.Now };
Parent p3 = new Parent() { ParentId = 3, ParentName = "Third", RelationshipStart = DateTime.Now };
Child c1 = new Child() { ChildId = 1, ChildName = "First" };
c1.Parents.Add(p1);
c1.Parents.Add(p2);
Child c2 = new Child() { ChildId = 2, ChildName = "Second" };
c2.Parents.Add(p2);
c2.Parents.Add(p3);
h.Children.Add(c1);
h.Children.Add(c2);
Serialize(h, AppContext.BaseDirectory + "Text.xml");
Hierarchy hReadBack = Deserialize(AppContext.BaseDirectory + "Text.xml");
}
Edit : To answer your question
Use these classes
public class Hierarchy
{
public Hierarchy()
{
Children = new List<Child>();
}
public List<Child> Children { get; set; }
private Dictionary<int, string> _parents;
private Dictionary<int, string> _childrenList;
private List<Relationship> _relationships;
private void CalcuateLists()
{
_parents = new Dictionary<int, string>();
_childrenList = new Dictionary<int, string>();
_relationships = new List<Relationship>();
foreach (Child c in this.Children)
{
if (!_childrenList.ContainsKey(c.ChildId))
{
_childrenList.Add(c.ChildId, c.ChildName);
}
foreach (Parent p in c.Parents)
{
if (!_parents.ContainsKey(p.ParentId))
{
_parents.Add(p.ParentId, p.ParentName);
}
if (_relationships.FirstOrDefault(dat => dat.ParentId == p.ParentId && dat.ChildId == c.ChildId) == null)
{
_relationships.Add(new Relationship() { ChildId = c.ChildId, ParentId = p.ParentId, Start = p.RelationshipStart });
}
}
}
}
public Dictionary<int, string> Parents {
get
{
if (_parents == null)
CalcuateLists();
return _parents;
}
}
public Dictionary<int, string> ChildrenList {
get
{
if (_childrenList == null)
CalcuateLists();
return _childrenList;
}
}
public List<Relationship> Relationships {
get
{
if (_relationships == null)
CalcuateLists();
return _relationships;
}
}
}
public class Child
{
public Child()
{
Parents = new List<Parent>();
}
[XmlAttribute("ChildId")]
public int ChildId { get; set; }
[XmlAttribute("ChildName")]
public string ChildName { get; set; }
public List<Parent> Parents { get; set; }
}
public class Parent
{
[XmlAttribute("ParentId")]
public int ParentId { get; set; }
[XmlAttribute("ParentName")]
public string ParentName { get; set; }
[XmlAttribute("RelationshipStart")]
public DateTime RelationshipStart { get; set; }
}
Then your test code becomes
public static void Test()
{
Hierarchy h = new Hierarchy();
Parent p1 = new Parent() { ParentId = 1, ParentName = "First", RelationshipStart = DateTime.Now };
Parent p2 = new Parent() { ParentId = 2, ParentName = "Second", RelationshipStart = DateTime.Now };
Parent p3 = new Parent() { ParentId = 3, ParentName = "Third", RelationshipStart = DateTime.Now };
Child c1 = new Child() { ChildId = 1, ChildName = "First" };
c1.Parents.Add(p1);
c1.Parents.Add(p2);
Child c2 = new Child() { ChildId = 2, ChildName = "Second" };
c2.Parents.Add(p2);
c2.Parents.Add(p3);
h.Children.Add(c1);
h.Children.Add(c2);
Serialize(h, AppContext.BaseDirectory + "Text.xml");
Hierarchy hReadBack = Deserialize(AppContext.BaseDirectory + "Text.xml");
Dictionary<int, string> Parents = hReadBack.Parents;
Dictionary<int, string> Children = hReadBack.ChildrenList;
List<Relationship> Relationships = hReadBack.Relationships;
}
EDIT
To get the results directly without looping
You will need this class
public class Relationship
{
public int ParentId { get; set; }
public int ChildId { get; set; }
public DateTime Start { get; set; }
}
And this selection
// Get a list of child ids and names
Dictionary<int, string> Children = (from c in hReadBack.Children select new { ChildId = c.ChildId, Name = c.ChildName}).ToDictionary(dat => dat.ChildId, dat => dat.Name);
// Get a parent ids and names
Dictionary<int, string> Parents = (from p in hReadBack.Children.SelectMany(i => i.Parents) select new { ParentId = p.ParentId, Name = p.ParentName }).Distinct().ToDictionary(dat => dat.ParentId, dat => dat.Name);
// Get the relationships
List<Relationship> Relationship = (from Child c in hReadBack.Children from Parent p in c.Parents select new Relationship() { ChildId = c.ChildId, ParentId = p.ParentId, Start = p.RelationshipStart }).ToList();

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

Build tree type list by recursively checking parent-child relationship C#

I have One class that has a list of itself so it can be represented in a tree structure.
I am pulling a flat list of these classes and want to unflatten it.
public class Group
{
public int ID {get;set;}
public int? ParentID {get;set;}
public List<Group> Children {get;set;}
}
I want to be able to do the following
List<Group> flatList = GetFlatList() //I CAN ALREADY DO THIS
List<Group> tree = BuildTree(flatList);
The ParentID related to the ID property on its parent group if that wasnt obvious.
EDIT
There is some confusion as to why I am returning a list and not a single object.
I am building a UI element that has a list of items, each of why has a child. So the initial list DOES NOT have a root node. It seems all of the solutions so far do not work.
What this means is I essentially need a list of tree type structures using Group class.
I have no idea why you want your BuildTree method return List<Group> - tree needs to have root node, so you should expect it to return single Group element, not a list.
I would create an extension method on IEnumerable<Group>:
public static class GroupEnumerable
{
public static IList<Group> BuildTree(this IEnumerable<Group> source)
{
var groups = source.GroupBy(i => i.ParentID);
var roots = groups.FirstOrDefault(g => g.Key.HasValue == false).ToList();
if (roots.Count > 0)
{
var dict = groups.Where(g => g.Key.HasValue).ToDictionary(g => g.Key.Value, g => g.ToList());
for (int i = 0; i < roots.Count; i++)
AddChildren(roots[i], dict);
}
return roots;
}
private static void AddChildren(Group node, IDictionary<int, List<Group>> source)
{
if (source.ContainsKey(node.ID))
{
node.Children = source[node.ID];
for (int i = 0; i < node.Children.Count; i++)
AddChildren(node.Children[i], source);
}
else
{
node.Children = new List<Group>();
}
}
}
Usage
var flatList = new List<Group>() {
new Group() { ID = 1, ParentID = null }, // root node
new Group() { ID = 2, ParentID = 1 },
new Group() { ID = 3, ParentID = 1 },
new Group() { ID = 4, ParentID = 3 },
new Group() { ID = 5, ParentID = 4 },
new Group() { ID = 6, ParentID = 4 }
};
var tree = flatList.BuildTree();
Here's how you can do this in one line:
static void BuildTree(List<Group> items)
{
items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());
}
You can just call it like this:
BuildTree(flatList);
If at the end you want to get the nodes whose parent is null (i.e. the top-level nodes), you can simply do this:
static List<Group> BuildTree(List<Group> items)
{
items.ForEach(i => i.Children = items.Where(ch => ch.ParentID == i.ID).ToList());
return items.Where(i => i.ParentID == null).ToList();
}
And if you want to make it an extension method, you can just add this in the method signature:
static List<Group> BuildTree(this List<Group> items)
Then you can call it like this:
var roots = flatList.BuildTree();
I tried solutions suggested and figured out that they give us about O(n^2) complexity.
In my case (I have about 50k items to be built into tree) it was completely unacceptable.
I came to the following solution (assuming that each item has only one parent and all parents exist in the list) with complexity O(n*log(n)) [n times getById, getById has O(log(n)) complexity]:
static List<Item> BuildTreeAndReturnRootNodes(List<Item> flatItems)
{
var byIdLookup = flatItems.ToLookup(i => i.Id);
foreach (var item in flatItems)
{
if (item.ParentId != null)
{
var parent = byIdLookup[item.ParentId.Value].First();
parent.Children.Add(item);
}
}
return flatItems.Where(i => i.ParentId == null).ToList();
}
Full code snippet:
class Program
{
static void Main(string[] args)
{
var flatItems = new List<Item>()
{
new Item(1),
new Item(2),
new Item(3, 1),
new Item(4, 2),
new Item(5, 4),
new Item(6, 3),
new Item(7, 5),
new Item(8, 2),
new Item(9, 3),
new Item(10, 9),
};
var treeNodes = BuildTreeAndReturnRootNodes(flatItems);
foreach (var n in treeNodes)
{
Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
}
}
// Here is the method
static List<Item> BuildTreeAndReturnRootNodes(List<Item> flatItems)
{
var byIdLookup = flatItems.ToLookup(i => i.Id);
foreach (var item in flatItems)
{
if (item.ParentId != null)
{
var parent = byIdLookup[item.ParentId.Value].First();
parent.Children.Add(item);
}
}
return flatItems.Where(i => i.ParentId == null).ToList();
}
class Item
{
public readonly int Id;
public readonly int? ParentId;
public Item(int id, int? parent = null)
{
Id = id;
ParentId = parent;
}
public readonly List<Item> Children = new List<Item>();
}
}
public class Item {
public readonly int Id;
public readonly int ? ParentId;
public Item(int id, int ? parent = null) {
Id = id;
ParentId = parent;
}
public readonly List < Item > Children = new List < Item > ();
}
public class BuildTree {
public static List < Item > BuildTreeAndReturnRootNodes(List < Item > flatItems) {
var byIdLookup = flatItems.ToLookup(i => i.Id);
foreach(var item in flatItems) {
if (item.ParentId != null) {
var parent = byIdLookup[item.ParentId.Value].First();
parent.Children.Add(item);
}
}
return flatItems.Where(i => i.ParentId == null).ToList();
}
}
public class TreeToFlatternBack {
public static IEnumerable < Item > GetNodes(Item node) {
if (node == null) {
yield
break;
}
yield
return node;
foreach(var n in node.Children) {
foreach(var innerN in GetNodes(n)) {
yield
return innerN;
}
}
}
}
class Program {
static void Main(string[] args) {
var flatItems = new List < Item > () {
new Item(1),
new Item(2),
new Item(3, 1),
new Item(4, 2),
new Item(5, 4),
new Item(6, 3),
new Item(7, 5),
new Item(8, 2),
new Item(9, 3),
new Item(10, 9),
};
Console.WriteLine();
Console.WriteLine("--------------------Build a Tree--------------------");
Console.WriteLine();
var treeNodes = BuildTree.BuildTreeAndReturnRootNodes(flatItems);
foreach(var n in treeNodes) {
Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
}
Console.WriteLine();
Console.WriteLine("--------------------Tree Back to Flattern--------------------");
Console.WriteLine();
List < Item > BackToflatItems = new List < Item > ();
foreach(var item in treeNodes) {
BackToflatItems.AddRange(TreeToFlatternBack.GetNodes(item));
}
foreach(var n in BackToflatItems.OrderBy(x => x.Id)) {
Console.WriteLine(n.Id + " number of children: " + n.Children.Count);
}
}
}

How can I construct a tree using this data structure

I have this tree structure, which a node may have multiple nodes.
public class Node
{
public Node()
{
ChildLocations = new HashSet<Node>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual int? ParentLocationId { get; set; }
public virtual ICollection<Node> ChildLocations { get; set; }
}
Now, I want to add a list of parent-child value into this structure. like:
{1,A} -> {2,B}
{1,A} -> {3,C}
{1,A} -> {4,D}
{3,C} -> {5,E}
{3,C} -> {6,F}
to construct a tree looks like this:
1A
/ | \
2B 3C 4D
/ \
5E 6F
finally, it returns the root reference.
I have come out this solution. But I have no confidence with recursive part. It this correct?
public class Tree
{
Node root;
public Node Root
{
get { return root; }
}
public void Add(int parentId, string parentName, int childId, string childName)
{
if (root == null)
{
root = new Node { Id = parentId, Name = parentName };
root.ChildLocations.Add(new Node { Id = childId, Name = childName });
}
else
{
Add(root, parentId, parentName, childId, childName);
}
}
private void Add(Node node, int parentId, string parentName, int childId, string childName)
{
if (node == null)
{
return;
}
if (node.Id == parentId)
{
node.ChildLocations.Add(new Node { Id = childId, Name = childName });
return;
}
foreach (var n in node.ChildLocations)
{
Add(n, parentId, parentName, childId, childName);
}
}
}
As per my comment to your question, this works to build the tree you require:
public Node BuildTree()
{
var _1A = new Node() { Id = 1, Name = "A", };
var _2B = new Node() { Id = 2, Name = "B", };
var _3C = new Node() { Id = 3, Name = "C", };
var _4D = new Node() { Id = 4, Name = "D", };
var _5E = new Node() { Id = 5, Name = "E", };
var _6F = new Node() { Id = 6, Name = "F", };
_1A.ChildLocations.Add(_2B);
_1A.ChildLocations.Add(_3C);
_1A.ChildLocations.Add(_4D);
_3C.ChildLocations.Add(_5E);
_3C.ChildLocations.Add(_6F);
return _1A;
}
But it's not very general purpose. Can you elaborate on your needs?

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