linq distinct or group by multiple properties
How can I using c# and Linq to get a result
from the next list:
var pr = new List<Product>()
{
new Product() {Title="Boots",Color="Red", Price=1},
new Product() {Title="Boots",Color="Green", Price=1},
new Product() {Title="Boots",Color="Black", Price=2},
new Product() {Title="Sword",Color="Gray", Price=2},
new Product() {Title="Sword",Color="Green",Price=2}
};
Result
:
{Title="Boots",Color="Red", Price=1},
{Title="Boots",Color="Black", Price=2},
{Title="Sword",Color="Gray", Price=2}
I know that I should use GroupBy
or Distinct
, but understand how to get what is needed
List<Product> result = pr.GroupBy(g => g.Title, g.Price).ToList(); //not working
List<Product> result = pr.Distinct(...);
Please help
Solution 1:
It's groups by needed properties and select:
List<Product> result = pr.GroupBy(g => new { g.Title, g.Price })
.Select(g => g.First())
.ToList();
Solution 2:
While a new anonymous type will work, it might make more sense, be more readable, and consumable outside of your method to either create your own type or use a Tuple. (Other times it may simply suffice to use a delimited string: string.Format({0}.{1}, g.Title, g.Price)
)
List<Product> result = pr.GroupBy(g => new Tuple<string, decimal>(g.Title, g.Price))
.ToList();
List<Product> result = pr.GroupBy(g => new ProductTitlePriceGroupKey(g.Title, g.Price))
.ToList();
As for getting the result set you want, the provided answer suggests just returning the first, and perhaps that's OK for your purposes, but ideally you'd need to provide a means by which Color
is aggregated or ignored.
For instance, perhaps you'd rather list the colors included, somehow:
List<Product> result = pr
.GroupBy(g => new Tuple<string, decimal>(g.Title, g.Price))
.Select(x => new Product()
{
Title = x.Key.Item1,
Price = x.Key.Item2,
Color = string.Join(", ", x.Value.Select(y => y.Color) // "Red, Green"
})
.ToList();
In the case of a simple string property for color, it may make sense to simply concatenate them. If you had another entity there, or simply don't want to abstract away that information, perhaps it would be best to have another entity altogether that has a collection of that entity type. For instance, if you were grouping on title and color, you might want to show the average price, or a range of prices, where simply selecting the first of each group would prevent you from doing so.
List<ProductGroup> result = pr
.GroupBy(g => new Tuple<string, decimal>(g.Title, g.Price))
.Select(x => new ProductGroup()
{
Title = x.Key.Item1,
Price = x.Key.Item2,
Colors = x.Value.Select(y => y.Color)
})
.ToList();