I have a class, that contains a list of classes, which in them selves, contains a list of classes, as below:
public class grouping
{
public List<Member> members { get; set; }
public string group_id { get; set; }
}
public class Member
{
public string member_id { get; set; }
public List<vStat> vstats { get; set; }
}
public class vStat
{
public string t1 { get; set; }
public string t2 { get; set; }
public string t3 { get; set; }
public string v_id { get; set; }
}
Each List vstats contains a number of vStats, where v_id only occurs once per list, but each members vstats list will repeat these v_id's. t1,2 and 3 are all integers, but unfortunately are stored in strings. I could cast them earlier if needs be, but was hoping to do all this in one go.
What I want to do is combine each Members vstats list into a single list for all members, with a single value for each v_id and the sum of the values for t1,t2,t3:
I can do this with a number of loops inside loops, but it seems there should be a more efficient way of doing this.
members.SelectMany(m => m.vstats)
.GroupBy(v => v.v._id)
.Select(g => new vStat {
t1 = g.Sum(x => Int32.Parse(x.t1)).ToString(),
t2 = g.Sum(x => Int32.Parse(x.t2)).ToString(),
t3 = g.Sum(x => Int32.Parse(x.t3)).ToString(),
v_id = g.Key
});
This query returns single list of aggregated vStat objects for all members.
If you have List<grouping> then you can first flatten its members and then its vStats using SelectMany and then you can project the result like:
List<grouping> groupList = new List<grouping>();
var result = groupList.SelectMany(grp=> grp.members)
.SelectMany(mem=> mem.vstats)
.GroupBy(v => v.v_id)
.Select(vgrp=> new vStat
{
v_id = vgrp.Key,
t1 = vgrp.Sum(r=> int.Parse(r.t1)).ToString(),
t2 = vgrp.Sum(r=> int.Parse(r.t2)).ToString(),
t3 = vgrp.Sum(r=> int.Parse(r.t3)).ToString()
});
Not really sure how much faster it is as compare to your loop code since it internally uses loops.
Related
I have two list with objects:
class Class1
{
public int Id { get; set; }
public string Name { get; set; }
public Guid UniqueIdentifier { get; set; }
public bool IsValid { get; set; }
}
class Class2
{
public int Identifier { get; set; }
public string Producer{ get; set; }
public Guid Guid { get; set; }
public int SupplierId { get; set; }
}
Is there a way to use linq to get the elements of type Class1 from the list that have the same Id (identifier) and Guid with the elements of type Class2 from the second list?
Here is one way to do it:
var result = list1
.Where(x => list2.Any(y => x.Id == y.Identifier && x.UniqueIdentifier == y.Guid))
.ToList();
Please note that this version is not optimized for large lists. If your lists are small, this is fine. If you have large lists, you need a solution that involves things like HashSets. Here is an example:
var list2HashSet = CreateHashset(list2.Select(x => new {x.Identifier, x.Guid}));
var result = list1
.Where(x => list2HashSet.Contains(new {Identifier = x.Id, Guid = x.UniqueIdentifier}))
.ToList();
Where CreateHashset is defined like this:
public static HashSet<T> CreateHashset<T>(IEnumerable<T> collection)
{
return new HashSet<T>(collection);
}
I had to create this simple method because the compiler is complaining that it cannot resolve the correct constructor overload. I am not really sure why.
You could try something like this:
var result = from item1 in list1
join item2 in list2 on
new { G = item1.UniqueIdentifier, Id = item1.Id }
equals new { G = item2.Guid, Id = item2.Identifier }
select new { item1, item2 };
foreach(var item in result)
{
Console.WriteLine($"Producer: {item.item2.Producer} with product: {item.item1.Name}");
}
Let's say you have the two lists
List<Class1> List1;
List<Class2> List2;
You can select all items of List1 containing the Id and Guid of the second list with
List1.Where(C1 => List2.Any(C2 => C1.Id == C2.Identifier && C1.UniqueIdentifier == C2.Guid));
Note that Guid is a class. If you don't want to check if C1.UniqueIdentifier and C2.Guid are exactly the same objects, you should implement IsEqual and use it like
List1.Where(C1 => List2.Any(C2 => C1.Id == C2.Identifier && C1.UniqueIdentifier.Equals(C2.Guid)));
If it suffice for you that the ids or the guids match, see the answer from Jeroen van Langen. Otherwise, I see two options:
Add a where clause afterwards, i.e.,
var result = from item1 in list1
join item2 in list2 on item1.Id equals item2.Identifier
where item1.UniqueIdentifier = item2.Guid
select new { item1, item2 };
Create a tuple class and join on the tuples of Guid and Id. You cannot reuse the .NET 4 tuple types (Tuple<,>), but you could reuse the C# 7 tuple types as they correctly implement equality.
Both versions should also be fine with large lists. Basically, the whole thing should scale as long as you use join.
I am aggregating data that I retrieve from multiple identical web services. The same row count and data points are returned with only a variance in the Value. The GroupBy clause I am using is not condensing any of the rows. I have the same row count before and after the GroupBy.
MyWebServiceUrls
.AsParallel()
.SelectMany(url => GetMetricItemData(url))
.GroupBy(item => new { item.DateTime, item.Group, item.Metric }, item => item.Value)
.Select(grp => new MetricItem()
{
DateTime = grp.Key.DateTime,
Group = grp.Key.Group,
Metric = grp.Key.Metric,
Value = // type = decimal?
grp.Any(mi => mi.HasValue)
? grp.Key.Metric.AggregationType == Metric.MetricAggregationTypes.Sum
? grp.Sum(mi => mi.Value)
: grp.Average(mi => mi)
: null
})
.AsEnumerable();
The syntax looks correct based on other examples I have found.
I send this data back to my database and can aggregate with the statement GROUP BY [DateTime], [Group], [Metric] and everything works great. While I can use the database to solve this issue, I would like to know how to correctly use LINQ in this instance.
What am I missing to get this LINQ expression to work?
UPDATE:
This is the relevant MetricItem and Metric class definition:
public class MetricItem
{
public string Group { get; set; }
public DateTime DateTime { get; set; }
public Metric Metric { get; set; }
public Decimal? Value { get; set; }
}
public class Metric
{
public string Code { get; set; }
public string Label { get; set; }
private List<string> SumMetrics = new List<string>(new string[] { "TPI", "TPO", "TPIO" });
public enum MetricAggregationTypes { Sum, Average };
public MetricAggregationTypes AggregationType
{
get
{
if (SumMetrics.IndexOf(this.Code) >= 0)
return MetricAggregationTypes.Sum;
else
return MetricAggregationTypes.Average;
}
}
}
You need to override Equals and GetHashCode on the Metric class. Most Linq methods use hash codes for comparison operations, so for most objects you define yourself, you need to override this class if you plan to use something like GroupBy, Union, etc.
I have an object ChartObject that contains a list. Then I have a list of ChartObjects that I used LINQ to remove duplicate attributes. But I don't know how to combine each"itemList" property of the ChartObjects.
public class ChartObject
{
public string Type { get; set; }
public int Year { get; set; }
public long Cost { get; set; }
public List<Items> itemList { get; set; }
}
List<ChartObject> coList = GetItems();
result = coList.GroupBy(i => new { i.Type, i.Year }).Select(g => new ChartObject
{
Type = g.Key.Type,
Year = g.Key.Year,
Cost = g.Sum(i => i.Cost),
itemList = g.Select(i => i.itemList) //Does not work
}).ToList();
I'm assuming it's not as simple as just casting the itemList as the compiler says and there must be some sort of aggregation to be done?
Assuming you just want to combine all of the items in the group, you're looking for SelectMany, and you'll need to call ToList to get a List<Items>:
g.SelectMany(i => i.itemList).ToList();
I unable to come up with a linq query for the following scenario.
public class Product
{
public virtual string ProductName { get; set; }
public virtual IList<SubProduct> SubProducts { get; set; }
}
public class SubProduct
{
public string SubProductName { get; set; }
public int SubProductTypeId { get; set; }
}
public class SubProductType
{
public int SubProductTypeId{ get; set; }
public string Description { get; set; }
}
var productList = List<Product>();
var subProductTypeLlist = List<SubProductType>();
I have a list of products and each product has list of SubProducts. I want to get the query to represent {ProductName, Description}. Please suggest how to write linq query.
Something like this should do the trick:
var result = productList
.SelectMany(p => p.SubProducts
.Select(sp => new { SubProduct = sp, ProductName = p.ProductName }))
.Select(sp =>
new { Description = subProductTypeList
.Single(spt => spt.SubProduct.SubProductTypeId == sp.SubProductTypeId).Description,
ProductName = sp.ProductName })
In the SelectMany, we first do a Select on the internal IEnumerable (IList implements IEnumerable) to convert each SubProduct object to an anonymous class holding the SubProduct object and the ProductName. The SelectMany then converts that to a flat list. We then use Select on that list to create a new anonymous class again, where this time, we grab the Description from subProductTypeList. The result is an IEnumerable of an anonymous class with the members Description and ProductName.
I have two classes Teams and PlayerTeams
public class PlayerTeams
{
public int ID { get; set; }
public string PlayerName { get; set; }
public string PlayerCountry { get; set; }
public bool IsActive { get; set; }
public string PlayerTeam { get; set; }
}
public class Players
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public bool? Status { get; set; }
}
I have a list of PlayerTeams which is grouped by PlayerTeam like this.
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy( x => x.PlayerTeam)
.ToList();
Its of type List<IGrouping<string, PlayerTeams>> but I want it to be of type List<IGrouping<string, Players>> as I do not want the redundant key information on every row.
How could I possibly achieve that? I could only think of something like .ConvertAll() on the IGrouping. I am not able to make it also.
Is there an efiicient way to do this?
If you can change the grouping, I'd just use:
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.ToList();
Where Players.FromPlayerTeam is a static method in Players which takes a PlayerTeam and returns a Players.
Additionally, I'd suggest using ToLookup instead of GroupBy - a Lookup is exactly what you want here, without bothering with the ToList call.
This not testet, just an idea.
If you have trouble converting your linq statement, which is expecting the IGrouping type, to a string list, then you might have to select it before.
var groupedPlayers = new List<string>();
groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.Select(x => x.Key) // << added select
.ToList();