Group nested list with linq - c#

I have a nested list of objects. That I need to group by identifierA and Sum its numeric properties, nested list shall group respectively:
public class TypeA
{
public String identifierA{ get; set; }
public Int32 number { get; set; }
public List<TypeB> nestedList { get; set; }
}
public class TypeB
{
public String identifierB { get; set; }
public Int32 otherNumber { get; set; }
}
So I'm expecting something like this:
var List<TypeA> groupedList = (from a in TypeAList
group a by a.identifierA
into groupedData
select new TypeA
{
identifierA = groupedData.Key,
number = groupedData.Sum(g => g.number ),
nestedList = //HOW TO GROUP NESTED PART?
}).ToList();

I think that this will resolve your issue.
List<TypeA> list = TypeAList
.GroupBy(a => a.identifierA)
.Select(
g =>
new TypeA
{
identifierA = g.Key,
number = g.Sum(n => n.number),
nestedList =
g.SelectMany(l => l.nestedList)
.GroupBy(b => b.identifierB)
.Select(
gg =>
new TypeB
{
identifierB = gg.Key,
otherNumber = gg.Sum(b => b.otherNumber)
}).ToList()
}).ToList();

SelectMany takes an IEnumerable<SomethingWithAnIEnumerable> and flattens all the SomethingWithAnIEnumerable's selected IEnumerables into a single IEnumerable:
nestedList = groupedData.SelectMany(pa => pa.nestedList).ToList()

use SelectMany
if you want to group the list into one use
nestedList = groupedData.SelectMany(d=>d.nestedList)
and if you want Sum of that list, use
nestedList = groupedData.SelectMany(d=>d.nestedList).Sum(o=>o.otherNumber)

I think your IdentfierB might be a key of some kind, and your result should reflect Grouped and Summed TypeBs.
List<TypeA> groupedList = TypeAList
.GroupBy(a => a.identifierA)
.Select(g => new TypeA()
{
identierA = g.Key,
number = g.Sum(a => a.number)
nestedList = g.SelectMany(a => a.nestedList)
.GroupBy(b => b.identifierB)
.Select(g2 => new TypeB()
{
identifierB = g2.Key,
otherNumber = g2.Sum(b => b.otherNumber)
}
}

Related

Get groups of elements from name inside of list C#

I have a List of :
public class GT
{
public string ActivityName { get; set; }
public int Seconds { get; set; }
}
And the ActivityName could repeat through the iteration and what I want to do is, regroup all the items that have the same name and calculate the average of seconds of these specifics items. I tried to use Distinct but it didn't group them.
You should use GroupBy instead of Distinct:
List<GT> list = /*...*/;
var query = list.GroupBy(x => x.ActivityName, (k,g) => new { ActivityName = k, AverageTimeInSeconds = g.Average(x => x.Seconds) });
var groups = items.GroupBy(p => p.ActivityName)
.Select(g => new
{
ActivityName = g.Key,
Average = g.Average(t => t.Seconds)
}).ToList();

C# Linq convert collection using groupby

I have a list of a class that looks like this
public class MyClass
{
public ComplexType A {get;set;}
public ComplexTypeB B {get;set;}
}
var myList = new List<MyClass>();
I then have a target Dto which looks like this
public class MyTargetDto
{
public ComplexType A {get;set;}
public List<ComplexTypeB> ListOfB {get;set;}
}
It's very similar only that myTargetDto supports grouping by ComplexType
Given a flat list of MyClass, how can I (using Linq) convert it to a target list of MyTargetDto?
You should do something like this.
myList.GroupBy(x=>x.A)
.Select(x=> new MyTargetDto()
{
A= x.Key,
ListOfB = x.Select(s=>s.B).ToList()
});
If your ComplexType does not have an own implementation of Equals than at first you would need to implement an IEqualityComparer<ComplexType>:
public class Comparer : IEqualityComparer<ComplexType>
{
public bool Equals(ComplexType x, ComplexType y)
{
// code to check your complex type for equality
}
public int GetHashCode(ComplexType obj)
{
return obj.GetHashCode();
}
}
Then you can use this comparer to group your list using GroupBy:
List<MyClass> flatList = ...
List<MyTargetDto> result = flatList.GroupBy(e => e.A, e => e.B, new Comparer())
.Select(g => new MyTargetDto {
A = g.Key,
ListOfB = g.ToList()});
.ToList();
If ComplexType already has an own implementation of Equals that works appropriatly, than you can ommit that comparer:
List<MyTargetDto> result = flatList.GroupBy(e => e.A, e => e.B)
.Select(g => new MyTargetDto {
A = g.Key,
ListOfB = g.ToList()})
.ToList();
The first lambda of GroupBy selects the element by which the list is grouped. This will then be the Key property in the resulting IGrouping.
The second lambda selects the elements that should be contained in that group.
The final Select creates for each group a MyTargetDto, setting it's A property to the ComplexType and creating the ListOfB.
myList.GroupBy(item => item.A)
.Select(group => new MyTargetDto
{
A = group.Key,
ListOfB = group.Select(item => item.B).ToList()
});
As already posted by others, a solution would be
myList.GroupBy(x => x.A, x => x.B)
.Select(g => new MyTargetDto()
{
A = g.Key,
ListOfB = g.ToList()
});
Just wanted you to see an existing shortcut using a GroupBy overload
myList.GroupBy(x => x.A, x => x.B, (key, g) => new MyTargetDto()
{
A = key,
ListOfB = g.ToList()
});

C# LINQ sub-where

I have object
public class OrderItem
{
public string idProduct { get; set; }
public int quantity { get; set; }
public List<WarehouseItem> WarehouseInfo = new List<WarehouseItem>();
}
public class WarehouseItem
{
public string Name{ get; set; }
public string LocnCode{ get; set; }
}
and i need select items which have WarehouseInfo.LocnCode == "A1"
It is doesnt work when I use something like
var items = itemList.Where(x => x.WarehouseInfo.Where(y => y.LocnCode.Equals("A1")));
Your requirements could be interpreted one of three ways, so here's three solutions:
Give me all OrderItems where ANY WarehouseItem has a LocnCode of "A1":
var items = itemList.Where(i => i.WarehouseInfo.Any(w => w.LocnCode == "A1"));
Give me all WarehouseItems within the OrderItems that have a LocnCode of "A1":
var items = itemList.SelectMany(i => i.WarehouseInfo)
.Where(w => w.LocnCode.Equals("A1"));
Give me all OrderItems where ANY WarehouseItem has a LocnCode of "A1", and filter WarehouseInfo to only those WarehouseItems:
This can't be done in a simple Linq query because there's no way to change the contents of the existing objects. You're going to have to create new objects with the filtered values:
var items = itemList.Where(i => i.WarehouseInfo.Any(w => w.LocnCode == "A1"))
.Select(i => new OrderItem
{
idProduct = i.idProduct,
quantity = i.quantity,
WarehouseInfo = i.WarehouseInfo.Where(w => w.LocnCode.Equals("A1"));
.ToList()
}
);
Try
var items = itemList.Where(x => x.WarehouseInfo.Any(y => y.LocnCode.Equals("A1")));
The Where takes a predicate that should return a bool. Any will return true if at least one item in the collection returns true for the given predicate.

Normalize objects using linq

I have an IEnumerable<RuleSelection> with these properties:
public class RuleSelection{
public int RuleId { get; set;}
public int? CriteriaId { get; set; }
public int? CriteriaSourceId{ get; set; }
}
RuleId in RuleSelection is not unique.
Can I write a linq query to normalize these into IEnumerable<Rule> which would be:
public class Rule{
public int RuleId { get; set; }
public IEnumerable<int> Criteria { get; set; }
public IEnumerable<int> CriteriaSource { get; set; }
}
Rule.RuleId would be unique and the properties Criteria and CriteriaSource would include all the CriteriaId's and CriteriaSourceId's for the RuleId respectively.
It sounds like you want something like:
var rules = selections.GroupBy(rs => rs.RuleId)
.Select(g => new Rule {
RuleId = g.Key,
Criteria = g.Select(rs => rs.CriteriaId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
CriteriaSource = g.Select(rs => rs.CriteriaSourceId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
});
Using my FullOuterGroupJoin extension method
LINQ - Full Outer Join
you could:
theRules.FullOuterGroupJoin(theRules,
r => r.RuleId,
r => r.RuleId,
(crit, critSource, id) => new Rule {
RuleId = id,
Criteria = crit
.Where(r => r.CriteriaId.HasValue)
.Select(r => r.CriteriaId.Value),
CriteriaSource = critSource
.Where(r => r.CriteriaSourceId.HasValue)
.Select(r => r.CriteriaSourceId.Value),
}
);
To write this:
var rules =
from sel in selections
group sel by sel.RuleId into rg
select new Rule {
RuleId = rg.Key,
Criteria = rg.Select(r => r.CriteriaId).FilterValues(),
CriteriaSource = rg.Select(r => r.CriteriaSourceId).FilterValues(),
};
I created the following FilterValues extension (to eliminate duplication):
public static IEnumerable<T> FilterValues<T>(
this IEnumerable<T?> items)
where T : struct
{
// omitting argument validation
return
from item in items
where item.HasValue
select item.Value;
}
I set out to provide essentially a pure query-syntax version of JonSkeet's answer. I gave up on that in effort to remove duplication for the property assignments and wound up with this combination extension & query-syntax approach.

Cast EF query result to extended type with extended property taken from EF query

I've got a Tag object:
public class Tag
{
public int TagID { get; set; }
public string Name { get; set; }
public virtual ICollection<Job> Jobs { get; set; }
public Tag()
{
Jobs = new HashSet<Job>();
}
}
and extended:
public class RecentTag : Tag
{
public int Count { get; set; }
}
...and I'm trying to retrieve a list of RecentTag objects with Count from the query added to each object:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
var tags = Jobs
.Where(j => j.DatePosted > DateTime.Now.AddDays(-(numberofdays)))
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new
{
RecentTag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
// return RecentTags { TagID, Name, Count, Jobs }
}
So, how do I cast results of the query to RecentTag type and return the list of extended objects?
Any help would be appreciated. Thanks in advance!
if Jobs is actually a collection of Tags, and they are in fact RecentTag objects, then you can simply use the Cast method.
var rtags = tags.Cast<RecentTags>;
However, if Jobs is not a collection of tags, then you need to project into a RecentTags objects..
var rtags = tags.Select(x => new RecentTags() { // assign the members });
I ended up doing:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
DateTime startdate = DateTime.Now.AddDays(-(numberofdays));
IEnumerable<RecentTag> tags = Jobs
.Where(j => j.DatePosted > startdate) // Can't use DateTime.Now.AddDays in Entity query apparently
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new RecentTag
{
TagID = k.TagID,
Name = k.Name,
Count = g.Count()
})
.OrderByDescending(g => g.Count)
.Select(a => a);
return tags;
}

Categories