I am having a bit of problem in that I am trying to GroupBy using linq and although it works, it only works when I eliminate one element of the code.
nestedGroupedStocks = stkPositions.GroupBy(x => new { x.stockName,
x.stockLongshort,x.stockIsin, x.stockPrice })
.Select(y => new stockPos
{
stockName = y.Key.stockName,
stockLongshort = y.Key.stockLongshort,
stockIsin = y.Key.stockIsin,
stockPrice = y.Key.stockPrice,
stockQuantity = y.Sum(x => x.stockQuantity)
}).ToList();
The above code Groups my stock positions and the results in the list containing 47 entries but what it fails to do is sum duplicate stocks with different quantities...
nestedGroupedStocks = stkPositions.GroupBy(x => new { x.stockName,
x.stockIsin, x.stockPrice })
.Select(y => new stockPos
{
stockName = y.Key.stockName,
stockIsin = y.Key.stockIsin,
stockPrice = y.Key.stockPrice,
stockQuantity = y.Sum(x => x.stockQuantity)
}).ToList();
However, if I elimanate "x.longshort" then I get the desired result, 34 stocks summed up, but the then all longshort elements in the list are null...
Its driving me nuts :-)
This part
.GroupBy(x => new { x.stockName,x.stockLongshort,x.stockIsin, x.stockPrice })
is the problem. You are trying to group the elements by that new object as key, but x.stockLongshort will most likely change for every single element in the list, making the GroupBy fail unless the name and the stockLongshort will match in both elements ( as for the other 2 fields, but I assume those are always the same).
nestedGroupedStocks = stkPositions.GroupBy(x => x.stockName)
.Select(y => new stockPos
{
stockName = y.First().stockName,
stockLongshort = y.First().stockLongshort,
stockIsin = y.First().stockIsin,
stockPrice = y.First().stockPrice,
stockQuantity = y.Sum(z => z.stockQuantity)
}).ToList();
Note that the stockLongshort property is set to be equal to the value of the first element in the group. You could set it to 0 if that's more usefull to you.
Longer Explanation
GroupBy returns IEnumerable<IGrouping<TKey, TSource>> , that is, a "set" (that you can enumarte) of Groups, with each element of the same group sharing the same Key, that you have defined with the lambda expression in the argument.
If you put x.stockLongshort as a property of the Key object, that becomes a discriminant of the evaluation made by GroupBy, that, as a consequence, puts two elements that differ just by that property in two distinct groups.
Related
I have a simple class:
class Balls
{
public int BallType;
}
And i have a really simple list:
var balls = new List<Balls>()
{
new Balls() { BallType = 1},
new Balls() { BallType = 1},
new Balls() { BallType = 1},
new Balls() { BallType = 2}
};
I've used GroupBy on this list and I want to get back the key which has the highest count/amount:
After I used x.GroupBy(q => q.BallType) I tried to use .Max(), but it returns 3 and I need the key which is 1.
I also tried to use Console.WriteLine(x.GroupBy(q => q.Balltype).Max().Key); but it throws System.ArgumentException.
Here's what I came up with:
var mostCommonBallType = balls
.GroupBy(k => k.BallType)
.OrderBy(g => g.Count())
.Last().Key
You group by the BallType, order by the count of items in the group, get the last value (since order by is in an ascending order, the most common value would be the last) and then return it's key
Some came up with the idea to order the sequence:
var mostCommonBallType = balls
.GroupBy(k => k.BallType)
.OrderBy(g => g.Count())
.Last().Key
Apart from that it is more efficient to OrderByDescending and then take the FirstOrDefault, you also get in trouble if your collection of Balls is empty.
If you use a different overload of GroupBy, you won't have these problems
var mostCommonBallType = balls.GroupBy(
// KeySelector:
k => k.BallType,
// ResultSelector:
(ballType, ballsWithThisBallType) => new
{
BallType = ballType,
Count = ballsWithThisBallType.Count(),
})
.OrderByDescending(group => group.Count)
.Select(group => group.BallType)
.FirstOrDefault();
This solves the previously mentioned problems. However, if you only need the 1st element, why would you order the 2nd and the 3rd element? Using Aggregate instead of OrderByDescending will enumerate only once:
Assuming your collection is not empty:
var result = ... GroupBy(...)
.Aggregate( (groupWithHighestBallCount, nextGroup) =>
(groupWithHighestBallCount.Count >= nextGroup.Count) ?
groupWithHighestBallCount : nextGroup)
.Select(...).FirstOrDefault();
Aggregate takes the first element of your non-empty sequence, and assigns it to groupWithHighestBallCount. Then it iterates over the rest of the sequence, and compare this nextGroup.Count with the groupWithHighestBallCount.Count. It keeps the one with the hightes value as the next groupWithHighestBallCount. The return value is the final groupWithHighestBallCount.
See that Aggregate only enumerates once?
I have a function that's main goal to group items by id, count them and finally order them by count.
var Db = list.Select(x => new
{
Id = x
});
var groups = Db.GroupBy(g => new { Id = g.Id })
.Select(g => new
{
Id = g.Key.Id,
Cnt = g.Count()
}).Distinct().OrderBy(g => g.Cnt).ToList();
Do you have any idea, where the error might be? It is not ordering. I have a bunch of low counts at first then it becomes random.
'list' is a one-dimensional list with many duplicated Ids (string) .
This is the output its giving out: ( I do not understand why there's duplicates in the ID since I'm only grouping by one key).
Link to output : https://pastebin.com/NjqJ4Aub
I'm trying to select the Id and Description from an EF table, but with distinct on Description.
I realize there's no SelectFirst, but I think this will help describe what I'm trying to do (I saw it suggested to use GroupBy/SelectFirst as a way of trying to apply Distinct to a specific column):
var results = _db.Certifications
.GroupBy(c => c.Description)
.SelectFirst(c => new SearchCriterion {Id = c.CertificationId, Name = c.Description});
FirstOrDefault() on the end doesn't work ("arguments cannot be inferred from the usage") and my properties (CertificationId/Description) aren't being recognized.
Almost, you have to use an aggregate on non grouped by properties. The code below, using Max, will return the largest CertificationId based on each distinct description. You can change that to Min to get smallest CertificationId.
var results = _db.Certifications
.GroupBy(c => c.Description)
.Select(c => new SearchCriterion {Id = c.Max(y => y.CertificationId), Name = c.Key});
I have a some code to sort my collection in linq in C#. I want it to group by the houseName to sum over the volumes, order that collection, but also pass a third parameter, pctVol, to the new sorted collection. What am I doing wrong? I know that the problem lies in the pctVol = group.Selecct(item => item.pctVol) line.
var inBetween = this.GroupBy(item => item.houseName)
.Select(group =>
new DataItem
{
houseName = group.Key,
VOLUME = group.Sum(item => item.VOLUME),
pctVol = group.Select(item => item.pctVol)
})
.ToList();
ObservableCollection<DataItem> objSort = new ObservableCollection<DataItem>(inBetween.OrderBy(DataItem =>
DataItem.VOLUME));
return objSort;
What kind of value do you want pctVol to have? With that code, it looks like DataItem.pctVol will be an IEnumerable containing all the pctVol values in that group.
If you want a single value, and all the pctVol values in each group are guaranteed to be the same, then you could just take the value from the first element, like this: pctVol = group.First().pctVol
I have the following block of code which works fine;
var boughtItemsToday = (from DBControl.MoneySpent
bought in BoughtItemDB.BoughtItems
select bought);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(boughtItemsToday);
It returns data from my MoneySpent table which includes ItemCategory, ItemAmount, ItemDateTime.
I want to change it to group by ItemCategory and ItemAmount so I can see where I am spending most of my money, so I created a GroupBy query, and ended up with this;
var finalQuery = boughtItemsToday.AsQueryable().GroupBy(category => category.ItemCategory);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
Which gives me 2 errors;
Error 1 The best overloaded method match for 'System.Collections.ObjectModel.ObservableCollection.ObservableCollection(System.Collections.Generic.List)' has some invalid arguments
Error 2 Argument 1: cannot convert from 'System.Linq.IQueryable>' to 'System.Collections.Generic.List'
And this is where I'm stuck! How can I use the GroupBy and Sum aggregate function to get a list of my categories and the associated spend in 1 LINQ query?!
Any help/suggestions gratefully received.
Mark
.GroupBy(category => category.ItemCategory); returns an enumerable of IGrouping objects, where the key of each IGrouping is a distinct ItemCategory value, and the value is a list of MoneySpent objects. So, you won't be able to simply drop these groupings into an ObservableCollection as you're currently doing.
Instead, you probably want to Select each grouped result into a new MoneySpent object:
var finalQuery = boughtItemsToday
.GroupBy(category => category.ItemCategory)
.Select(grouping => new MoneySpent { ItemCategory = grouping.Key, ItemAmount = grouping.Sum(moneySpent => moneySpent.ItemAmount);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
You can project each group to an anyonymous (or better yet create a new type for this) class with the properties you want:
var finalQuery = boughtItemsToday.GroupBy(category => category.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});
The AsQueryable() should not be needed at all since boughtItemsToday is an IQuerable anyway. You can also just combine the queries:
var finalQuery = BoughtItemDB.BoughtItems
.GroupBy(item => item.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});