List of descendant ID - children, grandchildren etc - c#

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

Related

How to search Hierarchical Data with Linq in list

Distributor Registration.
I want to fill list with following information about the distributor
Id
ParentId
Name
For each distributor, it must be determined on whose recommendation the distributor is registered in the system. This can be anyone already registered in the system - in which case they must be selected from a list of already registered distributors, or the referrer information can be blank, which means that the distributor registers in the system without a recommender. Each distributor can bring up to three people on its recommendation, the system should ensure that no more than three people are registered "under" the same distributor. Also, the depth of the hierarchy of the mentioned distributors should be maximum 5 - that is, the distributor can bring a person with a recommendation, who will also bring a person with his recommendation, etc. Maximum 5 levels. Accordingly, in such a group there can be a total of 1 + 3 + 9 + 27 + 81 = 121 people. The system must provide control over the given levels.
You can use recursion to find the depth of any element in the list and a plain old count to find the number of referrers.
The following code is an implementation of that idea.
void Main()
{
var distributors = new List<Distributor> {
new Distributor { Id = 1, ParentId = 0, Name = "A" },
new Distributor { Id = 7, ParentId = 1, Name = "B" },
new Distributor { Id = 9, ParentId = 7, Name = "C" },
new Distributor { Id = 13, ParentId = 9, Name = "D" },
new Distributor { Id = 28, ParentId = 13, Name = "E" },
};
var valid = IsValidToAdd(distributors, 9);
Console.WriteLine(valid);
}
public bool IsValidToAdd(List<Distributor> distributors, int parentId)
{
var referCount = distributors.Count(d => d.ParentId == parentId);
Console.WriteLine($"refer:{referCount}");
if (referCount == 3)
{
Console.WriteLine("There are already 3 referals for this parent");
return false;
}
var level = GetLevel(distributors, parentId);
Console.WriteLine($"level: {level}");
if (level > 5)
{
Console.WriteLine("There are already 5 levels of referals");
return false;
}
return true;
}
public int GetLevel(List<Distributor> distributors, int parentId)
{
var parent = distributors.FirstOrDefault(d => d.Id == parentId);
if (parent == null)
{
return 1;
}
return 1 + GetLevel(distributors, parent.ParentId);
}
public class Distributor
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}

How can I convert a list of items with ParentID into a tree?

I'm using SQL Server and Entity Framework. On my database I have the following data:
ID | Name | ParentID
1 | Fire | null
2 | Fire2 | 1
3 | Fire3 | 2
4 | Blast | 2
5 | Water | null
6 | Water2 | 5
7 | WaterX | 5
I won't have massive data, so retrieving everything at once from the database is perfectly acceptable.
I want to retrieve this data and display on screen as a "tree".
Fire
Fire2
Fire3 Blast
Water
Water2 WaterX
How should I do this? Should I create some sort of recursion to render it? Should I somehow convert the list to an IGrouping?
I'm having trouble converting the flat list into something that I can render hierarchically on the screen, how could I do that?
This is the easiest way I know:
var things = new []
{
new { Id = 1, Name = "Fire", ParentId = (int?)null },
new { Id = 2, Name = "Fire2", ParentId = (int?)1 },
new { Id = 3, Name = "Fire3", ParentId = (int?)2 },
new { Id = 4, Name = "Blast", ParentId = (int?)2 },
new { Id = 5, Name = "Water", ParentId = (int?)null },
new { Id = 6, Name = "Water2", ParentId = (int?)5 },
new { Id = 7, Name = "Waterx", ParentId = (int?)5 }
};
var tree = things.ToLookup(x => x.ParentId, x => new { x.Id, x.Name });
The tree looks like this:
That should be fairly easy to render now.
If you can add another property to your class that has the child items like this:
public class Thing
{
public Thing()
{
Things = new List<Thing>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public List<Thing> Things { get; set; }
}
Then you can easily group the items to their parents like this:
var things = new List<Thing>
{
new Thing { Id = 1, Name = "Fire", ParentId = null },
new Thing { Id = 2, Name = "Fire2", ParentId = 1 },
new Thing { Id = 3, Name = "Fire3", ParentId = 2 },
new Thing { Id = 4, Name = "Blast", ParentId = 2},
new Thing { Id = 5, Name = "Water", ParentId = null },
new Thing { Id = 6, Name = "Water2", ParentId = 5 },
new Thing { Id = 7, Name = "Waterx", ParentId = 6 }
};
var groupedThings = new List<Thing>();
foreach (var thing in things)
{
if (thing.ParentId != null)
{
things.First(t => t.Id == thing.ParentId).Things.Add(thing);
}
else
{
groupedThings.Add(thing);
}
}
groupedThings.Dump();

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

Efficient ordering of hierarchy data with only parentId

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.

Tree structure to get parent node and its children

Please find the table structure below:
Folderid parentFolderid Guid
1 0 1234
2 1 5678
3 2 9012
4 3 87697
5 7 4443
The requirement is if I pass folderId the function has to give me all the guids.
For example:If I pass 1 to the function, I should get first four Guids(parent and its children).
I have a function which returns all the guids as follows:
public List<Guid> Folders(int folderId)
{
// To get the folderids based on parentfolderid
var a = entity.Where(x => x.parentfolderId == folderId).FirstOrDefault();
return a;
}
I am able to get only up to one level of ids.
Is there any way to get parent, its children, grandchildren till the leaf?
If you are able to get that table to a class, check this out:
public class Entity
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Name { get; set; }
public static List<Entity> GetTree(int ID, List<Entity> ListToSearch, bool First = true)
{
List<Entity> FilteredEntities = new List<Entity>();
FilteredEntities.AddRange(ListToSearch.Where<Entity>(x => x.ParentID == ID).ToList<Entity>());
List<Entity> Temp = new List<Entity>();
foreach (Entity current in FilteredEntities)
{
Temp.AddRange(GetTree(current.ID, ListToSearch, false));
}
FilteredEntities.AddRange(Temp);
if (First)
{
FilteredEntities.Add(ListToSearch.Where<Entity>(x => x.ID == ID).Single<Entity>());
}
return FilteredEntities;
}
}
Usage:
List<Entity> filteredEntities = Entity.GetTree(1, entities);
List<string> onlyTheNames = filteredEntities.Select<Entity, string>(x => x.Name).ToList<string>();
Regards
If you use this node class, you can write the code like this.
public class Folder
{
public int Id { get; set; }
public int? ParentId { get; set; }
public Guid SomeGuid { get; set; }
}
and and example of what possible is:
var list = new List<Folder>
{
new Folder {Id = 0, ParentId = null, SomeGuid = new Guid("0000b25b-8538-4b78-818a-9094507e0000") },
new Folder {Id = 1, ParentId = 0, SomeGuid = new Guid("1000b25b-8538-4b78-818a-9094507e0001") },
new Folder {Id = 2, ParentId = 1, SomeGuid = new Guid("2000b25b-8538-4b78-818a-9094507e0002") },
new Folder {Id = 3, ParentId = 1, SomeGuid = new Guid("3000b25b-8538-4b78-818a-9094507e0003") },
new Folder {Id = 4, ParentId = 2, SomeGuid = new Guid("4000b25b-8538-4b78-818a-9094507e0004") },
new Folder {Id = 5, ParentId = 3, SomeGuid = new Guid("5000b25b-8538-4b78-818a-9094507e0005") },
new Folder {Id = 6, ParentId = 0, SomeGuid = new Guid("6000b25b-8538-4b78-818a-9094507e0006") },
new Folder {Id = 7, ParentId = 4, SomeGuid = new Guid("7000b25b-8538-4b78-818a-9094507e0007") },
new Folder {Id = 8, ParentId = 3, SomeGuid = new Guid("8000b25b-8538-4b78-818a-9094507e0008") },
};
var rootNode = Node<Folder>.CreateTree(list, n => n.Id, n => n.ParentId).Single();
var firstChild = rootNode.Children.First(); // Id 1
var descendentsOfFirstChild = firstChild.Descendants; // All descendants of node 1

Categories

Resources