LINQ: Counting Total Number of Nested Elements - c#

I have a data table that contains a one-to-many self referential relationship:
Plant
{
ID,
Name,
ParentID
}
I'm trying to create a linq query that will tell me the total number of descendants stemming from one Plant.
Example:
I have 5 plants:
One {ID = 1, Name = Pine, Parent = null};// This is the root
Two {ID = 2, Name = Evergreen, Parent = 1}
Three {ID = 3, Name = Winter Evergreen, Parent = 2}
Four {ID = 4, Name = Christmas Tree, Parent = 1}
Five {ID = 5, Name = Maple, Parent = null};// This is the root
When I call my LINQ query with an input of ID = 1, I want it to return 3, because there are 3 descendants of One; Two, Three and Four. Five is not a decedent of One.
The only way I can think about doing this involves nested recursive linq queries on the inner results. I have no idea how to do this and I feel as though there is an easier way.
I'm using C# 4.0 and LINQ if that matters.

If you were using SQL Server, the query you would want constructed would be:
DECLARE #TargetElementId int
SET #TargetElementId = 1;
WITH Plants AS
(
SELECT ID, Name, ParentId
FROM PlantsTable
WHERE ParentId = #TargetElementId
UNION ALL
SELECT ID, Name, ParentId
FROM PlantsTable
INNER JOIN Plants ON PlantsTable.ParentId = Plants.ID
)
SELECT COUNT(ID) - 1 FROM Plants
I don't believe this can be done with LinqToSql, see Common Table Expression (CTE) in linq-to-sql?, which is a question of a similar nature.

EDIT adding code that supports LINQ to SQL thanks to #Kirk Woll comment.
class Program
{
private static Table<Plant> plants;
static void Main(string[] args)
{
using (var dataContext = new DataClasses1DataContext())
{
plants = dataContext.Plants;
var treesNodes = GetTreesNodes(plants.Where(plant => plant.ID == 1));
Console.WriteLine(string.Join(",",
treesNodes.Select(plant => plant.ID)));
}
}
public static IEnumerable<Plant> GetTreesNodes(IEnumerable<Plant> roots)
{
if(!roots.Any())
{
return roots;
}
var children = roots.SelectMany(root =>
plants.Where(plant => plant.Parent == root));
return roots.Union(GetTreesNodes(children));
}
}
Previous version that match LINQ to Objects:
This method can provide all the nodes in the tree:
public IEnumerable<Plant> GetTreesNodes(IEnumerable<Plant> roots)
{
if(!roots.Any())
{
return Enumerable.Empty<Plant>();
}
var rootsIds = roots.Select(root => root.ID);
var children = plants.Where(plant => rootsIds.Contains(plant.Parent));
return roots.Union(GetTreesNodes(children));
}
It can be used as in the following example:
[Test]
public void ExampleTest()
{
var One = new Plant() {ID = 1, Name = "Pine", Parent = 0};
var Two = new Plant() {ID = 2, Name = "Evergreen", Parent = 1};
var Three = new Plant() {ID = 3, Name = "Winter Evergreen", Parent = 2};
var Four = new Plant() {ID = 4, Name = "Christmas Tree", Parent = 1};
var Five = new Plant() {ID = 5, Name = "Maple", Parent = 0};
plants = new []{One,Two,Three,Four,Five};
var nestedElements = GetTreesNodes(new[]{One});
var numOfNestedElementsWithoutRoot = nestedElements.Count()-1;
Assert.That(numOfNestedElementsWithoutRoot, Is.EqualTo(3));
}
The code assumes there are no cyclic references.

Related

Add duplicates together in List

First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();

Invert nested collections in LINQ

Having a list of type A, each containing a list of type B, what's the best way to get a list of all type B, each containing a list of type A to which they belong?
Having a list like the following:
var parents = new List<Parent> {
{
new Parent {
ID = 1,
Childs = new List<Child> {
{
new Child {
ID = 1
}
},
{
new Child {
ID = 2
}
},
{
new Child {
ID = 3
}
}
}
}
},
{
new Parent {
ID = 2,
Childs = new List<Child> {
{
new Child {
ID = 3
}
},
{
new Child {
ID = 4
}
},
{
new Child {
ID = 5
}
}
}
}
}
};
I would like to query this to receive the following result:
[
{
Child = 1,
InParent = [1]
},
{
Child = 2,
InParent = [1]
},
{
Child = 3,
InParent = [1, 2]
},
{
Child = 4,
InParent = [2]
},
{
Child = 5,
InParent = [2]
},
]
EDIT: I tried an approach to flatten the childs first using SelectMany & Distinct, but not sure how to link this to the parent again:
var foo =
from childId in parents.SelectMany(x => x.Childs).Select(x => x.ID).Distinct()
select
new
{
childId = childId,
inParent = // Missing Part
};
You have to use SelectMany first to flatten them, then use GroupBy to group by child-id and String.Join to concat each parent-id:
var childParents = parents
.SelectMany(p => p.Childs.Select(c => new {Parent = p, Child = c}))
.GroupBy(x => x.Child.ID)
.Select(g => new
{
Child = g.Key,
InParent = String.Join(", ", g.Select(x => x.Parent.ID))
});
Result:
If you actually don't want that InParent property is a string but a List<int>(or array) use this:
.....
InParent = g.Select(x => x.Parent.ID).ToList() // or ToArray()
You can split your large problem into two simpler problems:
Create an anonymous object for each parent/child pair, containing a reference to the parent as well as to the child. You can use a simple LINQ query with two from clauses for that.
Group those objects into the representation you need. The group by clause is your friend here.
I guess you should try changing your data model if you are looking for storing tree like structure, in that scenario you should always use single Linked List with custom object & nested reference to corresponding parent / child in the similar way you store in database.
It is an ideal way to handle data structures of such kind as such otherwise you will end up in many nested queries.

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

Select multiple fields group by and sum

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();

c# - AutoMapper: how to copy fields from 2 different sources into 1 destination

I have a flat file with a bunch of records, let's say it's a sequence of 2 record types
--- Record1: ID;NAME;SURNAME
--- Record2: AGE;SEX;
Let's call R1 the class representing Record1 and R2 the class representing Record2
In this moment I have an array of R1 and another array of R2
If I have a POCO called Subject that has 5 fields, named exactly as the union of the fields of R1 and R2, how do I configure AutoMapper to do the magic for me?
Now I'm trying this:
var subjects = Mapper.Map<IEnumerable<R1>, List<Subject>>(arrayOfR1s);
Mapper.Map<IEnumerable<R2>, List<Subject>>(arrayOfR2s, subjects);
After the first mapping, I get an array of Subjects, in every element of the array the fields ID, SURNAME, NAME are correctly filled with values. AGE and SEX are left to NULL as expected.
But after the second mapping, all the fields from R1 (ID, NAME, SURNAME) are initialized to NULL and I only get fields from R2 (AGE and SEX).
How do I get the complete union of the fields?
Can someone point me to the right approach?
How about the straightforward dynamic mapping of joined (anonymously typed) objects?
Record1[] firstRecords = new[]
{
new Record1
{
ID = Guid.NewGuid(),
Name = "John", Surname = "Doe"
},
new Record1
{
ID = Guid.NewGuid(),
Name = "Jane", Surname = "Roe"
}
};
Record2[] secondRecords = new[]
{
new Record2 { Age = 20, Sex = Sex.Male },
new Record2 { Age = 20, Sex = Sex.Female }
};
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new
{
ID = first.ID,
Name = first.Name,
Surname = first.Surname,
Age = second.Age,
Sex = second.Sex
};
return Mapper.DynamicMap<Subject>(r);
})
.ToArray();
By the way, you can map these object without using AutoMapper, but using LINQ Select().
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new Subject
{
ID = first.ID,
Name = first.Name,
Surname = first.Surname,
Age = second.Age,
Sex = second.Sex
};
return r;
})
.ToArray();
Update
If you need to copy a lot of properties, please take a look at the Value Injecter. InjectFrom() FTW!
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new Subject();
r.InjectFrom(first).InjectFrom(second);
return r;
})
.ToArray();

Categories