Group by in LinQ List [duplicate] - linq

This question already has answers here:
Group By Multiple Columns
(14 answers)
Linq Split properties of Class and assign it to another Custom Class
(1 answer)
Closed 8 years ago.
If i have a List that has values like
public class List
{
public string Category{ get; set; }
public string SubCategory{ get; set; }
public int Count { get; set; }
}
Eg:
Cat1
subCat1
3
Cat1
subCat2
4
Cat2
subCat2
2
Cat2
subCat3
4
How can i group the Categories and Show it?
i tried this
var list2 = list
.GroupBy(t=>t.Category,t=>t.Subcategory)
.Select(g=>(g)).ToList();
But looks like i am missing something

Related

Linq Group By - Unable to group on custom classes?

I have noticed that when I do a LINQ - group by, it only seems to be working if I dont group on any of my custom classes.
I have a Product class (shown below) and I would like to group on Product.Id ,Product.Variant (object) and on Product.Options (ICollection) (because my source list contains multiple times the same Product but with different Variants and / or Options)
Product:
public class Product
{
public int Id { get; set; }
public Variant Variant { get; set; }
public ICollection<Option> Options{ get; set; }
public string Name { get; set; }
public int Amount { get; set; }
}
The code below will do a grouping, but only on my Product.ID, when I try to also group on Variant / Options, I get no grouping (well, not the grouping I am intending to make) as it will return just as much items as my source list.
IEnumerable<productAndSum> productsAndSums = unmappedProducts
.GroupBy(prod => new { Id = prod.Id})
.Select(group => new productAndSum()
{
Key = group.Key,
Sum = group.Sum(x => x.Amount)
});
If I am on the right track and the issue is related to the Objects, then it might be usefull to add that also Option and Variant have multiple objects and collections themselves, or is this too deep?
Extra information: I first started to just group by my Product class (not Id, Variant & Options separately), but this was unsuccessful. so I started eliminating properties and this is how I found out this issue. I think that solving this issue will result in killing two birds with one stone.
Warm regards

Using LINQ to combine similar rows of grouped data

I've got some data in a table that looks like so:
Recipe | Category | Email
What I'd like to do is pull this data back from the source and put it into something that looks like so:
public class RecipeItem
{
public long Recipe { get; set; }
public long Category { get; set; }
public List<string> Names {get; set; }
}
Grouping by the Recipe and Category ids and putting all the emails that into the list.
So, what I've tried is to do something like this:
var recipeItems =
from entry in list
group entry by new { entry.Recipe, entry.Category}
into aRecipe
select new RecipeItem()
{
Recipe = aRecipe.Key.Recipe,
Category = aRecipe.Key.Category,
// ? Not sure how to stick the list of names in here
};
list is the data pulled back via entity framework.
But this isn't quite right - I think I'm close here (maybe). What am I missing here on this?
Follow-up:
Thanks to Aducci for clearing this up. The answer is that you can do this:
Names = aRecipe.Select(x => x.Name)
and this will add all those Names which are in each group into the Names collection for that group. Pretty nifty.
I would modify your class to look like this
public class RecipeItem
{
public long Recipe { get; set; }
public long Category { get; set; }
public IEnumerable<string> Names {get; set; }
}
And your link to entities query to:
var recipeItems =
from entry in list
group entry by new { entry.Recipe, entry.Category}
into aRecipe
select new RecipeItem()
{
Recipe = aRecipe.Key.Recipe,
Category = aRecipe.Key.Category,
Names = aRecipe.Select(x => x.Name)
};

Two dimensional list in a Grid

My case: I have a bunch of variables. These variables are shared between all Environments. In each Environment these variables have several values, each value connected to a "EnvironmentVariableSet". The amount of EnvironmentVariableSets vary from Environment to Environment. The number of variables are fixed.
For a given Environment I want to display, in a Grid, all the variables, and for each EnvironmentVariableSet, the value of each variable. Something like this:
Column 1 : The variables
Column 2 : The values of the variable in set1
...
Column N : The values of the variable in setN
The goal is that for each variable, the user can edit the value of that variable in a given EnvironmentVariableSet for a given Environment.
My Models:
public class VariableValues
{
public int VariableID { get; set; }
public string VariableName { get; set; }
public List<EnvironementVariableSetValue> environmentSetValues { get; set; }
}
public class EnvironementVariableSetValue
{
public String EnvironmentVariableValue { get; set; }
public int VariableID { get; set; }
}
My Grid
#(Html.Telerik().Grid(variableValues)
.Name("EnvironmentVariables")
//blabla
.Columns(columns =>
{
columns.Bound(v => v.VariableName).Width(50).ReadOnly();
//I NEED A WAY TO BIND EACH COLUMN TO THE UNDEFINED NUMBER ENVIRONMENTVARIABLESETS WITH VALUES HERE! SOMETHING LIKE THIS
//columns.Bound(v => v.ValueForSetN).Whitd(50);
})
//blabla
)
Maybe Im just stupid, but i cant find a good solution for this.
Thanks

Sorting an ICollection descending by average including nullable values

Given the following code:
class Book {
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Review> Reviews { get; set; }
}
class Review {
public int ID { get; set; }
public int? Overall { get; set; }
public string Comment { get; set; }
}
And assuming I have a list of every book in the database:
var books; // List<Book>
I would like to show the user the books ordered by the average review. Ideally, the list of books would have the highest average review at the top and sort descending. Given I have the overall rating in Review as a null-able integer, some books will not have reviews, let alone an overall score. I'm having difficulty sorting this data, I've tried seemingly every combination of:
books.OrderByDescending(b => b.Reviews.Average(a => a.Overall));
Is it possible to sort my list without creating a second list to get something like:
Book A (4.5/5)
Book B (4.3/5)
Book C (3.9/5)
Book D (3.5/5)
Book E (-/5)
Book F (-/5)
Thanks in advance.
You should be able to use this:
books.OrderByDescending(b =>
(b.Reviews == null) ? 0:b.Reviews.Average(r => r.Overall??0));
Thank you TheCodeKing for point me in the right direction. Here is what fixed the problem:
books.OrderByDescending(b => b.Reviews.Count() == 0 ? 0 : b.Reviews.Average(r => r.Overall ?? 0));

how to fill missing values from a list

I have an object containing a date and a count.
public class Stat
{
public DateTime Stamp {get; set;}
public int Count {get; set ;}
}
I have a Serie object that holds a list of thoses Stat plus some more info such as name and so on...
public class Serie
{
public string Name { get; set; }
public List<Stat> Data { get; set; }
...
}
Consider that I have a List of Serie but the series don't all contain the same Stamps.
I need to fill in the missing stamps in all series with a default value.
I thought of an extension method with signature like this (please provide better name if you find one :) ) :
public static IEnumerable<Serie> Equalize(this IEnumerable<ChartSerie> series, int defaultCount)
this question seems to treat the same problem, but when querying directly the DB. of course I could loop through the dates and create another list. But is there any more elegant way to achieve this?
i.e.:
Serie A:
01.05.2010 1
03.05.2010 3
Serie B:
01.05.2010 5
02.05.2010 6
I should get :
Serie A :
01.05.2010 1
02.05.2010 0
03.05.2010 3
Serie B:
01.05.2010 5
02.05.2010 6
03.05.2010 0
Not sure if this is elegant enough for you ;-) but since I like Linq, this is what I would have done (using your naming scheme):
public static IEnumerable<Serie> Equalize(
this IEnumerable<Serie> series,
int defaultCount)
{
var allStamps = series
.SelectMany(s => s.Data.Select(d => d.Stamp))
.Distinct()
.OrderBy(d => d)
.ToList();
return series.Select(serie => new Serie(
serie.Name,
allStamps.Select(d =>
serie.Data.FirstOrDefault(stat => stat.Stamp == d)
??
new Stat(d, defaultCount))
));
}
For this code to compile, your classes needs a couple of constructors:
public class Stat
{
public Stat() {}
public Stat(DateTime stamp, int count)
{
Stamp = stamp;
Count = count;
}
public DateTime Stamp { get; set; }
public int Count { get; set; }
}
public class Serie
{
public Serie() {}
public Serie(string name, IEnumerable<Stat> data)
{
Name = name;
Data = new List<Stat>(data);
}
public string Name { get; set; }
public List<Stat> Data { get; set; }
}
When calling series.Equalize(0) the code above will leave the original instances intact, and return a sequence of newly created Serie-instances with their Data padded with defaults.
Nothing magic about it. Just the sweetness of Linq... (and the null coalescing operator!)
I haven't tried this with loads and loads of data, so your milage may vary.

Resources