I have a table that holds a hierarchy as below
ParentChildMap
{
parent_id,
child_id
}
Another table holds the details of each member in the Map
Member_Details
{
Member_Id,
Member_Name
}
Sometimes the relation can be as simple as Parent--->Child or sometimes the relation can have multiple levels such as GG-GrandFather--> G-GrandFather---> GrandFather ---> Parent --->Child.
What I want to do is to list all Children of a given family with their details.
Can somebody help me with the most efficient LINQ query for this?
I realize this question has been unanswered for more than 4 years, but it seemed like a fun question. I think the following approach should work with a Linq query. I'm basically postulating that children are not parents. So if this is true, then a relationship left-join on itself should produce all nodes that are not parents. Once this is done a regular join to the details will match the name of the child node. Following is my example code:
void Main()
{
// 1
// 2 3
// 4
// 5 6
// Child nodes are all those that are not parents: i.e.: 5, 6, 3
var details = new[] {
new Member_Details { Member_Id = 1, Member_Name = "Node 1" },
new Member_Details { Member_Id = 2, Member_Name = "Node 2" },
new Member_Details { Member_Id = 3, Member_Name = "Node 3" },
new Member_Details { Member_Id = 4, Member_Name = "Node 4" },
new Member_Details { Member_Id = 5, Member_Name = "Node 5" },
new Member_Details { Member_Id = 6, Member_Name = "Node 6" },
};
var relationships = new[] {
new ParentChildMap { parent_id = 1, child_id = 2 },
new ParentChildMap { parent_id = 1, child_id = 3 },
new ParentChildMap { parent_id = 2, child_id = 4 },
new ParentChildMap { parent_id = 4, child_id = 5 },
new ParentChildMap { parent_id = 4, child_id = 6 }
};
var children = relationships
.GroupJoin(relationships, r1 => r1.child_id, r2 => r2.parent_id, (r1, r2) => r2
.Select(x => new { Inner = r1.child_id, Outer = x.child_id})
.DefaultIfEmpty(new { Inner = r1.child_id, Outer = 0 }))
.SelectMany(x => x)
.Where(x => x.Outer == 0)
.Join(details, r => r.Inner, d => d.Member_Id, (r, d) => new {Id = r.Inner, Name = d.Member_Name});
foreach (var child in children)
{
Console.WriteLine($"ID: {child.Id}, Name: {child.Name}");
}
}
public class ParentChildMap
{
public int parent_id;
public int child_id;
}
public class Member_Details
{
public int Member_Id;
public string Member_Name;
}
Related
I want to make one collection of three according to certain conditions (with LINQ). let's say I have a Class:
class Collection
{
public int id;
public string name;
public double weight;
}
Then I'm creating collections:
List<Collection> collection1 = new()
{
new Collection() { id = 11, name = "Abraham 1", weight = 1.1 },
new Collection() { id = 12, name = "Abraham 2", weight = 1.2 },
new Collection() { id = 13, name = "Abraham 3", weight = 1.3 },
};
List<Collection> collection2 = new()
{
new Collection() { id = 21, name = "Bill 1", weight = 2.1 },
new Collection() { id = 22, name = "Bill 2", weight = 2.2 },
new Collection() { id = 23, name = "Bill 3", weight = 2.3 },
};
List<Collection> collection3 = new()
{
new Collection() { id = 31, name = "David 1", weight = 3.1 },
new Collection() { id = 32, name = "David 2", weight = 3.2 },
new Collection() { id = 33, name = "David 3", weight = 3.3 },
};
TODO 1. Condition: get 1st column from 1st collection, 2nd column from 2nd collection, 3rd column from 3rd column. result should be:
{
new Collection() { id = 11, name = "Bill 1", weight = 3.1 },
new Collection() { id = 12, name = "Bill 2", weight = 3.2 },
new Collection() { id = 13, name = "Bill 3", weight = 3.1 }
}
TODO 2. Second case condition: get first elements from columns of all collections. result should be:
{
new Collection() { id = 11, name = "Abraham 1", weight = 1.1 },
new Collection() { id = 21, name = "Bill 1", weight = 2.1 },
new Collection() { id = 31, name = "David 1", weight = 3.1 }
}
Please help.
Using C# 10 and .Net 6, just use Enumerable.Zip:
var todo1 = collection1.Zip(collection2, collection3)
.Select(t => new Collection { id = t.First.id, name = t.Second.name, weight = t.Third.weight })
.ToList();
var todo2 = collection1.Zip(collection2, collection3)
.Select(t => new List<Collection> { t.First, t.Second, t.Third })
.First();
If you don't have the three argument Zip that returns a tuple, just roll your own, e.g. for Todo1:
var td1 = collection1.Zip(collection2, (c1, c2) => (c1, c2))
.Zip(collection3, (t12, c3) => (First: t12.c1, Second: t12.c2, Third: c3))
.Select(t => new Collection { id = t.First.id, name = t.Second.name, weight = t.Third.weight })
.ToList();
If you want to merge multiple collections into one collection using LINQ then do this:
List<Collection> result = collection1.Concat(collection2).OrderBy(x => x.id).ToList();
I have the below set of data
Where each City belongs to a specific Department, which belongs to a specific Region, which belongs to a specific Country (in this case there is only one country: France).
This data is contained in a CSV file which I can read from on a row-by-row basis, however my goal is to convert this data into a tree structure (with France being at the root).
Each of these nodes will be given a specific Id value, which is something I've already gone and done, but the tricky part is that each node here must also contain a ParentId (for instance Belley and Gex need the ParentId of Ain, but Moulins and Vichy need the ParentId of Aller).
Below is a snippet of code I've written that has assigned an Id value to each name in this data set, along with some other values:
int id = 0;
List<CoverageAreaLevel> coverageAreas = GetCoverageAreaDataFromCsv(path, true);
List<LevelList> levelLists = new List<LevelList>
{
new LevelList { Names = coverageAreas.Select(a => a.Level1).Distinct().ToList(), Level = "1" },
new LevelList { Names = coverageAreas.Select(a => a.Level2).Distinct().ToList(), Level = "2" },
new LevelList { Names = coverageAreas.Select(a => a.Level3).Distinct().ToList(), Level = "3" },
new LevelList { Names = coverageAreas.Select(a => a.Level4).Distinct().ToList(), Level = "4" }
};
List<CoverageArea> newCoverageAreas = new List<CoverageArea>();
foreach (LevelList levelList in levelLists)
{
foreach (string name in levelList.Names)
{
CoverageArea coverageArea = new CoverageArea
{
Id = id++.ToString(),
Description = name,
FullDescription = name,
Level = levelList.Level
};
newCoverageAreas.Add(coverageArea);
}
}
The levelLists variable contains a sort-of heirarchical structure of the data that I'm looking for, but none of the items in that list are linked together by anything.
Any idea of how this could be implemented? I can manually figure out each ParentId, but I'd like to automate this process, especially if this needs to be done in the future.
The solution from #Camilo is really good and pragmatic. I would also suggest the use of a tree.
A sample implementation:
var countries = models.GroupBy(xco => xco.Country)
.Select((xco, index) =>
{
var country = new Tree<String>();
country.Value = xco.Key;
country.Children = xco.GroupBy(xr => xr.Region)
.Select((xr, xrIndex) =>
{
var region = new Tree<String>();
region.Value = xr.Key;
region.Parent = country;
region.Children =
xr.GroupBy(xd => xd.Department)
.Select((xd, index) =>
{
var department = new Tree<String>();
department.Value = xd.Key;
department.Parent = region;
department.Children = xd
.Select(xc => new Tree<String> { Value = xc.City, Parent = department });
return department;
});
return region;
});
return country;
});
public class Tree<T>
{
public IEnumerable<Tree<T>> Children;
public T Value;
public Tree<T> Parent;
}
One way you could solve this is by building dictionaries with the names and IDs of each level.
Assuming you have data like this:
var models = new List<Model>
{
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept1", City = "FranceA" },
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept1", City = "FranceB" },
new Model { Country = "France", Region = "FranceRegionA", Department = "FranceDept2", City = "FranceC" },
new Model { Country = "France", Region = "FranceRegionB", Department = "FranceDept3", City = "FranceD" },
new Model { Country = "Italy", Region = "ItalyRegionA", Department = "ItalyDept1", City = "ItalyA" },
new Model { Country = "Italy", Region = "ItalyRegionA", Department = "ItalyDept2", City = "ItalyB" },
};
You could do something like this, which can probably be improved further if needed:
var countries = models.GroupBy(x => x.Country)
.Select((x, index) => Tuple.Create(x.Key, new { Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var regions = models.GroupBy(x => x.Region)
.Select((x, index) => Tuple.Create(x.Key, new { ParentId = countries[x.First().Country].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var departments = models.GroupBy(x => x.Department)
.Select((x, index) => Tuple.Create(x.Key, new { ParentId = regions[x.First().Region].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
var cities = models
.Select((x, index) => Tuple.Create(x.City, new { ParentId = departments[x.Department].Id, Id = index + 1 }))
.ToDictionary(x => x.Item1, x => x.Item2);
The main idea is to leverage the index parameter of the Select method and the speed of dictionaries to find the parent ID.
Sample output from a fiddle:
countries:
[France, { Id = 1 }],
[Italy, { Id = 2 }]
regions:
[FranceRegionA, { ParentId = 1, Id = 1 }],
[FranceRegionB, { ParentId = 1, Id = 2 }],
[ItalyRegionA, { ParentId = 2, Id = 3 }]
departments:
[FranceDept1, { ParentId = 1, Id = 1 }],
[FranceDept2, { ParentId = 1, Id = 2 }],
[FranceDept3, { ParentId = 2, Id = 3 }],
[ItalyDept1, { ParentId = 3, Id = 4 }],
[ItalyDept2, { ParentId = 3, Id = 5 }]
cities:
[FranceA, { ParentId = 1, Id = 1 }],
[FranceB, { ParentId = 1, Id = 2 }],
[FranceC, { ParentId = 2, Id = 3 }],
[FranceD, { ParentId = 3, Id = 4 }],
[ItalyA, { ParentId = 4, Id = 5 }],
[ItalyB, { ParentId = 5, Id = 6 }]
I have this model:
public class Node
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
}
I have The following data that comes from a database query:
nodes.Add(new Node { Id = 1, Name = "Node #1", ParentId = null });
nodes.Add(new Node { Id = 2, Name = "Node #2", ParentId = 1 });
nodes.Add(new Node { Id = 3, Name = "Node #3", ParentId = 2 });
nodes.Add(new Node { Id = 4, Name = "Node #4", ParentId = null });
nodes.Add(new Node { Id = 5, Name = "Node #5", ParentId = 2 });
nodes.Add(new Node { Id = 6, Name = "Node #6", ParentId = 2 });
nodes.Add(new Node { Id = 7, Name = "Node #7", ParentId = 1 });
nodes.Add(new Node { Id = 8, Name = "Node #8", ParentId = 5 });
nodes.Add(new Node { Id = 9, Name = "Node #9", ParentId = 4 });
nodes.Add(new Node { Id = 10, Name = "Node #10", ParentId = 4 });
I would like to sort the list and maintain the flat structure. The output I am expecting is this:
// 1 - Node #1 => NULL
// 2 - Node #2 => 1
// 3 - Node #3 => 2
// 5 - Node #5 => 2
// 8 - Node #8 => 5
// 6 - Node #6 => 2
// 7 - Node #7 => 1
// 4 - Node #4 => NULL
// 9 - Node #9 => 4
// 10 - Node #10 => 4
I was referring to this Stackoverflow answer but I didn't get the result I want.
Any help?
Here's how I would do it:
var nodes = new List<Node>()
{
new Node { Id = 1, Name = "Node #1", ParentId = null },
new Node { Id = 2, Name = "Node #2", ParentId = 1 },
new Node { Id = 3, Name = "Node #3", ParentId = 2 },
new Node { Id = 4, Name = "Node #4", ParentId = null },
new Node { Id = 5, Name = "Node #5", ParentId = 2 },
new Node { Id = 6, Name = "Node #6", ParentId = 2 },
new Node { Id = 7, Name = "Node #7", ParentId = 1 },
new Node { Id = 8, Name = "Node #8", ParentId = 5 },
new Node { Id = 9, Name = "Node #9", ParentId = 4 },
new Node { Id = 10, Name = "Node #10", ParentId = 4 },
};
var lookup = nodes.ToLookup(x => x.ParentId);
IEnumerable<Node> Flatten(int? parentId)
{
foreach (var node in lookup[parentId])
{
yield return node;
foreach (var child in Flatten(node.Id))
{
yield return child;
}
}
}
var output = Flatten(null).ToArray();
That little bit of recursion gives me:
I think you want this
nodes.OrderBy(n => n.ParentID ?? n.Id)
.ThenBy(n => n.Id);
You can try creating recursive query in LINQ using SelectMany as:
IEnumerable<Node> Recurcive(List<Node> nodeList, int? parentId)
{
return nodeList
.Where(x => x.ParentId == parentId)
.SelectMany(x =>
new[] { new Node
{ Id = x.Id, Name = x.Name, ParentId = x.ParentId } }
.Concat(Recurcive(nodeList, x.Id)));
}
foreach (var node in Recurcive(nodes, null))
Console
.WriteLine($"Id : {node.Id}\t, Name = {node.Name}\t, Parent = {node.ParentId}");
Output:
//Id: 1 , Name = Node #1 , Parent =
//Id: 2 , Name = Node #2 , Parent = 1
//Id: 3 , Name = Node #3 , Parent = 2
//Id: 5 , Name = Node #5 , Parent = 2
//Id: 8 , Name = Node #8 , Parent = 5
//Id: 6 , Name = Node #6 , Parent = 2
//Id: 7 , Name = Node #7 , Parent = 1
//Id: 4 , Name = Node #4 , Parent =
//Id: 9 , Name = Node #9 , Parent = 4
//Id: 10 , Name = Node #10 , Parent = 4
Apologies if this is asked elsewhere however I have searched around for a suitable answer, however most of the information I have found allows for single child item only.
I'm trying to write a program that displays information in a treeview style interface. The problem I'm facing is that I'm reading the following class from my backend DB:
public class InputClass
{
public int id { get; set; }
public string text { get; set; }
public string icon { get; set; }
public int? parentId { get; set; }
}
and I'm trying to convert it to a list of the following type:
public class OutputClass
{
public int id { get; set; }
public string text { get; set; }
public string icon { get; set; }
public List<OutputClass> children { get; set; }
}
as you can see, the children property will populate when it finds it's parent item.
As an example - the following list:
var inputList = new List<InputClass>();
inputList.Add(new InputClass() { id = 1, text = "Item #1"});
inputList.Add(new InputClass() { id = 2, text = "Item #2" });
inputList.Add(new InputClass() { id = 3, text = "Item #3" });
inputList.Add(new InputClass() { id = 4, text = "SubItem #1", parentId = 1 });
inputList.Add(new InputClass() { id = 5, text = "SubItem #2", parentId = 1 });
inputList.Add(new InputClass() { id = 6, text = "SubItem #3", parentId = 2 });
should output as:
Item #1
----SubItem #1
----SubItem #2
Item #2
----SubItem #3
Item #3
the number of elements in the children list should not be limited to just one. Any ideas on how to sort this properly?
var mapping = inputList
// for each input element, capture the parent id and create the respective output object
.Select(input => new {
ParentId = input.parentId,
Obj = new OutputClass() { id = input.id, text = input.text, icon = input.icon, children = new List<OutputClass>() }
})
// create a dictionary so we can look up the elements by id
.ToDictionary(x => x.Obj.id);
// create target list
List<OutputClass> output = new List<OutputClass>();
// loop through all elements
foreach (var x in mapping.Values)
{
// if the element has a parent id
if (x.ParentId.HasValue)
{
// find the parent object …
OutputClass parentObj = mapping[x.ParentId.Value].Obj;
// … and add this object to the parent’s child list
parentObj.children.Add(x.Obj);
}
else
{
// otherwise this is a root element, so add it to the target list
output.Add(x.Obj);
}
}
The result will be a list that contains the input elements with the respective hiearchy.
This is a solution in linear time, and it only loops through the items twice. Furthermore, it also supports hierarchies of multiple levels, so you could have items that have a parent id 5 producing a third level, etc.
To produce the output, you could write a recursive function like this:
public static void Print(IEnumerable<OutputClass> elements, string indent="")
{
foreach (OutputClass element in elements)
{
Console.WriteLine("{0}{1} {2}", indent, element.id, element.text);
Print(element.children, indent + " ");
}
}
For your input, this produces the following result:
1 Item #1
4 SubItem #1
5 SubItem #2
2 Item #2
6 SubItem #3
3 Item #3
And just as an example, the following input list produces the output below, using the same conversion code as above:
var inputList = new List<InputClass>()
{
new InputClass() { id = 1, text = "Item 1" },
new InputClass() { id = 2, text = "Item 2" },
new InputClass() { id = 3, text = "Item 3" },
new InputClass() { id = 4, text = "SubItem 1.1", parentId = 1 },
new InputClass() { id = 5, text = "SubItem 1.2", parentId = 1 },
new InputClass() { id = 6, text = "SubItem 2.1", parentId = 2 },
new InputClass() { id = 7, text = "SubItem 2.2", parentId = 2 },
new InputClass() { id = 8, text = "SubItem 1.2.1", parentId = 5 },
new InputClass() { id = 9, text = "SubItem 1.2.2", parentId = 5 },
new InputClass() { id = 10, text = "SubItem 1.2.1.1", parentId = 8 },
new InputClass() { id = 11, text = "SubItem 2.1.1", parentId = 6 },
new InputClass() { id = 12, text = "SubItem 2.1.1.1", parentId = 11 },
new InputClass() { id = 13, text = "SubItem 2.1.1.1.1", parentId = 12 },
new InputClass() { id = 14, text = "SubItem 2.1.1.1.2", parentId = 12 }
};
Output:
1 Item 1
4 SubItem 1.1
5 SubItem 1.2
8 SubItem 1.2.1
10 SubItem 1.2.1.1
9 SubItem 1.2.2
2 Item 2
6 SubItem 2.1
11 SubItem 2.1.1
12 SubItem 2.1.1.1
13 SubItem 2.1.1.1.1
14 SubItem 2.1.1.1.2
7 SubItem 2.2
3 Item 3
The following code should print what you need:
static void Main(string[] args)
{
var inputList = new List<InputClass>();
inputList.Add(new InputClass() { id = 1, text = "Item #1" });
inputList.Add(new InputClass() { id = 2, text = "Item #2" });
inputList.Add(new InputClass() { id = 3, text = "Item #3" });
inputList.Add(new InputClass() { id = 4, text = "SubItem #1", parentId = 1 });
inputList.Add(new InputClass() { id = 5, text = "SubItem #2", parentId = 1 });
inputList.Add(new InputClass() { id = 6, text = "SubItem #3", parentId = 2 });
var outputList = inputList
.Where(i => !i.parentId.HasValue) // Just get the parents
.Select(i => new OutputClass()
{
id = i.id,
icon = i.icon,
text = i.text,
children = inputList
.Where(x => x.parentId == i.id)
.Select(x => new OutputClass()
{
id = x.id,
icon = x.icon,
text = x.text,
}).ToList()
}).ToList();
foreach (var output in outputList)
{
Console.WriteLine(output.text);
output.children.ForEach(c => Console.WriteLine($"\t {c.text}"));
}
Console.ReadLine();
}
The output is:
Item #1
SubItem #1
SubItem #2
Item #2
SubItem #3
Item #3
I have a table for categories like this:
id parentid Title
1 0 Parent1
2 1 Child1
3 0 Parent2
4 3 Child2
5 1 anotherChild1
6 3 anotherChild2
How can I sort it in this order:
Parent1
child1
anotherchild1
Parent2
child2
anotherchild2
and save the order into another field called sorted index?
This should work for any level of hierarchy
public void CustomSort()
{
List<MyNode> nodes = new List<MyNode>();
nodes.Add(new MyNode() { Id = 5, ParentId = 1, Title = "Anotherchild1" });
nodes.Add(new MyNode() { Id = 6, ParentId = 3, Title = "Anotherchild2" });
nodes.Add(new MyNode() { Id = 7, ParentId = 6, Title = "Anotherchild3" });
nodes.Add(new MyNode() { Id = 1, ParentId = 0, Title = "Parent1" });
nodes.Add(new MyNode() { Id = 2, ParentId = 1, Title = "Child1" });
nodes.Add(new MyNode() { Id = 3, ParentId = 0, Title = "Parent2" });
nodes.Add(new MyNode() { Id = 4, ParentId = 3, Title = "Child2" });
Func<MyNode, List<int>> hierarchy = null;
hierarchy = n =>
{
var n2 = nodes.FirstOrDefault(x => x.Id == n.ParentId);
if (n2 != null) return hierarchy(n2).Concat(new List<int>() { n.Id }).ToList();
return new List<int>() { n.ParentId,n.Id };
};
var debug = nodes.Select(x=>hierarchy(x)).ToList();
var sortedList = nodes.OrderBy(n => String.Join(",", hierarchy(n).Select(x=>x.ToString("X8"))))
.ToList();
}
class MyNode
{
public int Id;
public int ParentId;
public string Title;
}
Id PId Title hierarchy (key to sort)
1 0 Parent1 0 1
2 1 Child1 0 1 2
5 1 Anotherchild1 0 1 5
3 0 Parent2 0 3
4 3 Child2 0 3 4
6 3 Anotherchild2 0 3 6
7 6 Anotherchild3 0 3 6 7
If your heirarchy has only two levels then you can join the parents with the children, flatten the resulting list, and then include the index for each person.
var parents = people.Where(p => p.ParentId < 1).ToList();
var children = people.Where(p => p.ParentId >= 1).ToList();
var ordered = (from parent in parents
join child in children on parent.Id equals child.ParentId into childGroup
from person in (new [] { parent }).Concat(childGroup)
select person)
.Select((person, sortedIndex) => new { person, sortedIndex });