Reputation: 9222
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.
Upvotes: 27
Views: 72288
Reputation: 2356
I had a need to do this as well as I am storing hierarchical data in a single SQL table. I had a handful of different types/tables that do this, so building on the other answers here I created a generic version.
It's pretty slow due to the nested loops and reflection, but my data sets are small so far.
Hopefully this helps someone.
DataModels:
namespace DataModels
{
public class Tag
{
public int TagId { get; set; }
public int? TagIdParent { get; set; }
public string TagName { get; set; }
public IEnumerable<Tag> Items { get; set; }
} // end class
public class Equipment
{
public int EquipmentId { get; set; }
public int? EquipmentIdParent { get; set; }
public string EquipmentName { get; set; }
public string? EquipmentDescription { get; set; }
public string? EquipmentPhotoUrl { get; set; }
public IEnumerable<Equipment> Items { get; set; }
} // end class
} // end namespace
LogicBase (implementation):
using DataModels
namespace Logic
{
public class LogicBase
{
public IEnumerable<T> BuildObjectTree<T>(List<T> data)
{
var id = $"{typeof(T).Name}Id";
var idParent = $"{typeof(T).Name}IdParent";
foreach (var dataItem1 in data)
{
var children = new List<T>();
var dataItem1Id = dataItem1.GetType().GetProperty(id).GetValue(dataItem1);
foreach (var dataItem2 in data)
{
var dataItem2Id = dataItem2.GetType().GetProperty(idParent).GetValue(dataItem2);
if (dataItem1Id.Equals(dataItem2Id)) // == doesn't work here
{
children.Add(dataItem2);
}
}
dataItem1.GetType().GetProperty("Items").SetValue(dataItem1, children);
}
var results = new List<T>();
foreach (var dataItem3 in data)
{
var dataItem3IdParent = dataItem3.GetType().GetProperty(idParent).GetValue(dataItem3);
if (dataItem3IdParent == null)
{
results.Add(dataItem3);
}
}
return results;
} // end
} // end class
} // end namespace
LogicTag (Usage):
using Data;
using DataModels;
namespace Logic
{
public interface ILogicTag
{
IEnumerable<Tag> RetrieveTags();
}
public class LogicTag(IDataTag dataTag) : LogicBase, ILogicTag
{
public IEnumerable<Tag> RetrieveTags()
{
var tags = dataTag.RetrieveTags().Result;
return BuildObjectTree(tags.ToList());
} // end
} // end class
} // end namespace
Upvotes: 0
Reputation: 334
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);
}
}
}
Upvotes: 0
Reputation: 1873
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>();
}
}
Upvotes: 11
Reputation: 101672
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();
Upvotes: 29
Reputation: 125620
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();
Upvotes: 51