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 });
Related
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
I have following data Structure and i want to put covert it in a Hierarchy based on RelationId. This is sorted on RelationId.
Id = 2 has relationId =2 and following two rows has realtionId =0 . That represent the Id=3 and Id=4 are child of Id = 2
Id Name RelationId SortOrder
1 A 1 1
2 B 2 2
3 C 0 3
4 D 0 4
5 E 3 5
6 F 0 6
7 G 0 7
8 H 4 8
End Result would be like following
Id = 1
|
Id = 2
|___ Id = 3 , Id = 4
Id = 5
|___ Id= 6 , Id=7
Id = 8
The desired result is as following (for simplicity representing it as List). This would be a List<Something> in C#
Result =
[
{ Id = 1, Name = A, Children = Null },
{ Id = 2, Name = B, Children = [{ Id = 3, Name = C }, {Id = 4, Name = D }] },
{ Id = 5, Name = E, Children = [{ Id = 6, Name = F }, {Id = 7, Name = G }] },
{ Id = 8, Name = H}
]
My unsuccessful attempt is as following
var finalResult = new List<sampleDataClass>();
var sampleData = GetMeSampleData();
var count = sampleData.Count();
foreach (var item in sampleData)
{
var alreadyExist = finalResult.Any(x => x.Id == item.Id);
var newObject = new sampleDataClass();
if (!alreadyExist && item.RelationId!= 0)
{
newObject = item;
}
for (int i = item.SortOrder; i < count; i++)
{
if (sampleData[i].RelationId== 0)
{
newObject.Children.Add(sampleData[i]);
}
}
finalResult.Add(newObject );
}
Since your RelationId decides whether it is a root or nested element, you could form a group based on these relation and do this. I would suggest using Linq
List<SomeData> somedata = ... // your data.
int index=0;
var results = somedata
.Select(x=> new {gid = x.RelationId ==0? index: ++index, item=x})
.GroupBy(x=> x.gid)
.Select(x=> {
var first = x.FirstOrDefault();
return new
{
Id = first.item.Id,
Name = first.item.Name,
Children = x.Skip(1).Select(s=> new {
Id = s.item.Id,
Name = s.item.Name,
})
};
})
.ToList();
Output :
Id=1, Name=A
Id=2, Name=B
Id=3, Name=C
Id=4, Name=D
Id=5, Name=E
Id=6, Name=F
Id=7, Name=G
Id=8, Name=H
Check this Working Code
I have done it like this. Don't know if there can be some more elegant solution
var data = new List<MyDataObject>();
var SampleData = GetMeSampleData;
var count = SampleData.Count();
for (int i=0;i<count;i++)
{
var rootAdded = false;
var relationId = SampleData[i].relationId;
var alreadyExist = data.Any(x => x.Id == SampleData[i].Id);
var mydataObject = new MyDataObject();
if (!alreadyExist && SampleData[i].RelationId != 0)
{
mydataObject = SampleData[i];
rootAdded = true;
}
for(int j=i+1;j<count;j++)
{
if ((SampleData[j].RelationId == 0 && rootAdded))
{
mydataObject.Children.Add(SampleData[j]);
}
if (SampleData[j].SubjectId != 0)
break;
}
if (rootAdded)
{
data.Add(mydataObject);
}
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;
}
i have these data:
class MyTableItem
{
public long id { get; set; }
public long listId { get; set; }
public long listFieldValue { get; set; }
public long parentId { get; set; }
}
and:
var myData = new MyTableItem[]
{
new MyTableItem { id = 1, listId = 1, listFieldValue = 100, parentId = 1 },
new MyTableItem { id = 2, listId = 2, listFieldValue = 130, parentId = 1 },
new MyTableItem { id = 3, listId = 3, listFieldValue = 170, parentId = 1 },
new MyTableItem { id = 4, listId = 4, listFieldValue = 170, parentId = 1 },
new MyTableItem { id = 5, listId = 1, listFieldValue = 100, parentId = 2 },
new MyTableItem { id = 6, listId = 2, listFieldValue = 130, parentId = 2 },
new MyTableItem { id = 7, listId = 3, listFieldValue = 170, parentId = 2 },
new MyTableItem { id = 8, listId = 4, listFieldValue = 270, parentId = 2 },
...(continue)
};
var myMatchConditions = new int?[][] //id, rangeTypeId(equal, more, less, between), from, to
{
new int?[] { 1, 1, 100, null },
new int?[] { 2, 2, 125, null },
new int?[] { 3, 3, null, 175 },
new int?[] { 4, 4, 130, 180 }
...(can continue more)
};
now i need to know which myData (groupBy parrentId) are matched by my conditions,
let me explain more:
I want to know which parrent Id has listFieldValue where:
1) (listId == 1)&&(listFieldValue == 100)
and
2) (listId == 2)&&(listFieldValue > 125)
and
3) (listId == 3)&&(listFieldValue < 175)
and
4) ((listId == 4)&&(listFieldValue > 130)&&(listFieldValue < 180))
it must return (1)parrentId.
There you go. Explanations are at the bottom:
IEnumurable<MyTableItem> temp = myData ;
for (int i = 0; i < myMatchConditions.GetLength(0); i++)
{
var conditionType = myMatchConditions[i,1];
if (conditionType == 1)
{
temp = temp.Where(_ => _listFieldValue == myMatchConditions[i,2]);
}
else
{
if (conditionType == 2 || conditionType == 4)
{
temp = temp.Where(_ => _listFieldValue > myMatchConditions[i,2]);
}
if (conditionType == 3 || conditionType == 4)
{
temp = temp.Where(_ => _listFieldValue < myMatchConditions[i,3]);
}
}
}
I'm using IEnumurable<MyTableItem> which means it's Linq and not Linq to entities. I chose that because your myData is not an EF table but a simple array.
I go through all the "rows" with a for, you can do that with a foreach, and I add the Where clauses to filter out more and more each time (The actual filtering will happen only when you use that temp list)
I add a condition based on the type in the second cell, and if the type is 4... I add both the 2 and 3 type rules... which makes a 4 type rule
I want to do a query with linq (list of objects) and I really don't know how to do it, I can do the group and the sum but can't select rest of the fields.
Example:
ID Value Name Category
1 5 Name1 Category1
1 7 Name1 Category1
2 1 Name2 Category2
3 6 Name3 Category3
3 2 Name3 Category3
I want to group by ID, SUM by Value and return all fields like this.
ID Value Name Category
1 12 Name1 Category1
2 1 Name2 Category2
3 8 Name3 Category3
Updated :
If you're trying to avoid grouping for all the fields, you can group just by Id:
data.GroupBy(d => d.Id)
.Select(
g => new
{
Key = g.Key,
Value = g.Sum(s => s.Value),
Name = g.First().Name,
Category = g.First().Category
});
But this code assumes that for each Id, the same Name and Category apply. If so, you should consider normalizing as #Aron suggests. It would imply keeping Id and Value in one class and moving Name, Category (and whichever other fields would be the same for the same Id) to another class, while also having the Id for reference. The normalization process reduces data redundancy and dependency.
void Main()
{
//Me being lazy in init
var foos = new []
{
new Foo { Id = 1, Value = 5},
new Foo { Id = 1, Value = 7},
new Foo { Id = 2, Value = 1},
new Foo { Id = 3, Value = 6},
new Foo { Id = 3, Value = 2},
};
foreach(var x in foos)
{
x.Name = "Name" + x.Id;
x.Category = "Category" + x.Id;
}
//end init.
var result = from x in foos
group x.Value by new { x.Id, x.Name, x.Category}
into g
select new { g.Key.Id, g.Key.Name, g.Key.Category, Value = g.Sum()};
Console.WriteLine(result);
}
// Define other methods and classes here
public class Foo
{
public int Id {get;set;}
public int Value {get;set;}
public string Name {get;set;}
public string Category {get;set;}
}
If your class is really long and you don't want to copy all the stuff, you can try something like this:
l.GroupBy(x => x.id).
Select(x => {
var ret = x.First();
ret.value = x.Sum(xt => xt.value);
return ret;
}).ToList();
With great power great responsibility comes. You need to be careful. Line ret.value = x.Sum(xt => xt.value) will change your original collection, as you are passing reference, not new object. If you want to avoid it, you need to add some Clone method into your class like MemberwiseClone (but again, this will create shallow copy, so be careful). Afer that just replace the line with: var ret = x.First().Clone();
try this:
var objList = new List<SampleObject>();
objList.Add(new SampleObject() { ID = 1, Value = 5, Name = "Name1", Category = "Catergory1"});
objList.Add(new SampleObject() { ID = 1, Value = 7, Name = "Name1", Category = "Catergory1"});
objList.Add(new SampleObject() { ID = 2, Value = 1, Name = "Name2", Category = "Catergory2"});
objList.Add(new SampleObject() { ID = 3, Value = 6, Name = "Name3", Category = "Catergory3"});
objList.Add(new SampleObject() { ID = 3, Value = 2, Name = "Name3", Category = "Catergory3"});
var newList = from val in objList
group val by new { val.ID, val.Name, val.Category } into grouped
select new SampleObject() { ID = grouped.ID, Value = grouped.Sum(), Name = grouped.Name, Category = grouped.Category };
to check with LINQPad:
newList.Dump();