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
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 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 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 am programming in silverlight (c# .net)
lets say I have a list of type "data"
public class data
{
public string QUOTE_ID { get; set; }
public string EVENT_ACTION_CD { get; set; }
public string CUSTOMER_NAME { get; set; }
public string ADAPTIV_CODE { get; set; }
}
the problem is some of the data comes from 1 database and the other data comes from another, so right now i get the data in 2 steps - so i have something like this (using random numbers):
input1 = new List<data> //data return from database 1
//(the data is actually returned as a datable which i convert to a list
//to put to a datagrid, but the end result is shown below)
{
new data { QUOTE_ID = "1", EVENT_ACTION_CD = "2"},
new Project { QUOTE_ID = "2", EVENT_ACTION_CD = "4"},
new Project { QUOTE_ID = "3", EVENT_ACTION_CD = "5"}
};
input2 = new List<data> //data return from database 2
{
new data { QUOTE_ID = "1", CUSTOMER_NAME = "2", ADAPTIV_CODE ="5"},
new Project { QUOTE_ID = "2", CUSTOMER_NAME = "4", ADAPTIV_CODE = "5"},
new Project { QUOTE_ID = "3", CUSTOMER_NAME = "5", ADAPTIV_CODE = "7"}
};
so i should have 2 lists like
input1:
(1, 2, null, null
2, 4, null, null
3, 5, null, null)
and
input2:
(1, null, 2, 5
2, null, 4, 5
3. null, 5, 7)
how do i join them together to form one input list to become
(1, 2, 2, 5
2, 4, 4, 5
3, 5, 5, 7)
Use linq with a join operator.
See http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
var resultList = (from item in input1
join item2 in input2 on item2.QUOTE_ID equals input2.QUOTE_ID
let item.CUSTOMER_NAME = item2.CUSTOMER_NAME
let item.ADAPTIV_CODE = item2.ADAPTIV_CODE
select item).ToList();
A normal for loop would work for you:
for(int i = 0; i < input1.Count; i++){
if(input1[i].QUOTE_ID == null) input1[i].QUOTE_ID = input2[i].QUOTE_ID;
if(input1[i].EVENT_ACTION_CD == null) input1[i].EVENT_ACTION_CD = input2[i].EVENT_ACTION_CD;
if(input1[i].CUSTOMER_NAME == null) input1[i].CUSTOMER_NAME = input2[i].CUSTOMER_NAME;
if(input1[i].ADAPTIV_CODE == null) input1[i].ADAPTIV_CODE = input2[i].ADAPTIV_CODE;
}
The result will be saved into the input1. The code also supposes input1 and input2 have the same Count.
var input3 = input1.Join(
input2,
d1 => d1.QUOTE_ID,
d2 => d2.QUOTE_ID,
(d1, d2) => new data() {
QUOTE_ID = d1.QUOTE_ID,
EVENT_ACTION_CD = d1.EVENT_ACTION_CD,
CUSTOMER_NAME = d2.CUSTOMER_NAME,
ADAPTIV_CODE = d2.ADAPTIV_CODE
}
);