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
Related
I have got this assignment. I need to create method which works with JSON data in this form:
On input N, what is top N of movies? The score of a movie is its average rate
So I have a JSONfile with 5 mil. movies inside. Each row looks like this:
{ Reviewer:1, Movie:1535440, Grade:1, Date:'2005-08-18'},
{ Reviewer:1, Movie:1666666, Grade:2, Date:'2006-09-20'},
{ Reviewer:2, Movie:1535440, Grade:3, Date:'2008-05-10'},
{ Reviewer:3, Movie:1535440, Grade:5, Date:'2008-05-11'},
This file is deserialized and then saved as a IEnumerable. And then I wanted to create a method, which returns List<int> where int is MovieId. Movies in the list are ordered descending and the amount of "top" movies is specified as a parameter of the method.
My method looks like this:
public List<int> GetSpecificAmountOfBestMovies(int amountOfMovies)
{
var moviesAndAverageGradeSortedList = _deserializator.RatingCollection()
.GroupBy(movieId => movieId.Movie)
.Select(group => new
{
Key = group.Key,
Average = group.Average(g => g.Grade)
})
.OrderByDescending(a => a.Average)
.Take(amountOfMovies)
.ToList();
var moviesSortedList = new List<int>();
foreach (var movie in moviesAndAverageGradeSortedList)
{
var key = movie.Key;
moviesSortedList.Add(key);
}
return moviesSortedList;
}
So moviesAndAverageGradeSortedList returns List<{int,double}> because of the .select method. So I could not return this value as this method is type of List<int> because I want only movieIds not their average grades.
So I created a new List<int> and then foreach loop which go through the moviesAndAverageGradeSortedList and saves only Keys from that List.
I think this solution is not correct because foreach loop can be then very slow when I put big number as a parameter. Does somebody know, how can I get "Keys" (movieIds) from the first list and therefore avoid creating another List<int> and foreach loop?
I will be thankful for every solution.
You can avoid the second list creation by just adding another .Select after the ordering. Also to make it all a bit cleaner you could:
return _deserializator.RatingCollection()
.GroupBy(i => i.Movie)
.OrderByDescending(g => g.Average(i => i.Grade))
.Select(g => g.Key)
.Take(amountOfMovies)
.ToList();
Note that this won't really improve performance much (if at all) because even in your original implementation the creation of the second list is done only on the subset of the first n items. The expensive operations are the ordering by the averages of the group and that you want to perform on all items in the json file, regardless to the number of item you want to return
You could add another select after you have ordered the list by average
var moviesAndAverageGradeSortedList = _deserializator.RatingCollection()
.GroupBy(movieId => movieId.Movie)
.Select(group => new
{
Key = group.Key,
Average = group.Average(g => g.Grade)
})
.OrderByDescending(a => a.Average)
.Take(amountOfMovies)
.Select(s=> s.Key)
.ToList();
So far, I have this:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)));
Configuration folder will contain pairs of files:
abc.json
abc-input.json
def.json
def-input.json
GetReportName() method strips off the "-input" and title cases the filename, so you end up with a grouping of:
Abc
abc.json
abc-input.json
Def
def.json
def-input.json
I have a ReportItem class that has a constructor (Name, str1, str2). I want to extend the Linq to create the ReportItems in a single statement, so really something like:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)))
**.Select(x => new ReportItem(x.Key, x[0], x[1]));**
Obviously last line doesn't work because the grouping doesn't support array indexing like that. The item should be constructed as "Abc", "abc.json", "abc-input.json", etc.
If you know that each group of interest contains exactly two items, use First() to get the item at index 0, and Last() to get the item at index 1:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)))
.Where(g => g.Count() == 2) // Make sure we have exactly two items
.Select(x => new ReportItem(x.Key, x.First(), x.Last()));
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x))).Select(x => new ReportItem(x.Key, x.FirstOrDefault(), x.Skip(1).FirstOrDefault()));
But are you sure there will be exactly two items in each group? Maybe has it sence for ReportItem to accept IEnumerable, not just two strings?
This will give me the list of string having "WIGS_AUTH_" in each item. Now I want to remove this part from the list items in the same expression. Or any better way to achieve this?
you can add another Select at the end
List<PermissionDto> ans = result
.Where(x => x.Contains("WIGS_AUTH_"))
.Select(x => new PermissionDto { Name = x.Replace("WIGS_AUTH_", "") })
.ToList();
you can try something like this:
result.Where(x => x.Contains("WIGS_AUTH")).Select(x => new Permision() { Name=x.Replace("WIGS_AUTH","") }).ToList();
First its filtering the result and taking items containing "WING_AUT"
now it is creating new object of PermissionDto and set name property without "WIGS_AUTH"
adding new object to list and returning back
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.
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)
});