Reputation: 795
I have a list of Product
s, with the Price
. I would like to get the the cheapest one only if it is unique. If there are more than one Product
with the same lowest price, it should not return any.
In the sample below, for the uniqProductList
the query should return the BestOne
while for the dupProductList
, no product should be returned.
How do I write the Linq query ?
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime ExpiryDate { get; set; }
}
List<Product> uniqProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
new Product { Name = "BestOne", Price = 9.99M }
};
List<Product> dupProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
};
Upvotes: 2
Views: 557
Reputation: 1
This is another solution :
var product = uniqProductList.OrderBy(a => a.Price)
.GroupBy(a => a.Price).FirstOrDefault()
.Aggregate(new List<Product>(), (result, item) =>
{
result.Add(item);
if (result.Count() > 1)
result = new List<Product>();
return result;
}).FirstOrDefault();
You can get the lowest price first, then you can group them.
Upvotes: 0
Reputation: 186698
You are looking for ArgMax
which is not included into standard Linq but can be implemented manually with a help of Aggregate
. Having a collection of the cheapest Product
s we can return null
if we have more than 1
of them:
using System.Linq;
...
List<Product> source = ...
var bests = source
.Aggregate(new List<Product>(), (s, a) => {
if (s.Count <= 0 || s[0].Price == a.Price)
s.Add(a);
else if (a.Price <= s[0].Price) {
s.Clear();
s.Add(a);
}
return s;
});
Product best = bests.Count == 1 ? bests[1] : default(Product);
Upvotes: 2
Reputation: 568
You could group by the elements by their price and getting the cheapest group:
var cheapestGrp = uniqProductList.GroupBy(i => i.Price).OrderBy(i => i.Key).First();
Then, based on the number of elements of the group, return the only element or return nothing:
if (cheapestGrp.Count() > 1)
return null;
else
return cheapestGrp.ToList().First();
Upvotes: 1
Reputation: 38114
You can use GroupBy
and then use Where
to get items where there is just one Count
and then just sort in ascending order:
var result = uniqProductList
.GroupBy(u => u.Price)
.Select(grp => new { grp.Key, Count = grp.Count(), Items = grp.ToList() })
.Where(s => s.Count == 1)
.OrderBy(o=> o.Key)
.FirstOrDefault();
An example:
List<Product> uniqProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
new Product { Name = "BestOne", Price = 9.99M }
};
List<Product> dupProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
};
var result = uniqProductList
.GroupBy(u => u.Price)
.Select(grp => new { grp.Key, Count = grp.Count(), Items = grp.ToList() })
.Where(s => s.Count == 1)
.OrderBy(o=> o.Key)
.FirstOrDefault();
Upvotes: 0
Reputation: 1863
I would suggest this solution:
public Product? TryGetBestOne(IEnumerable<Product> products)
{
var bestProducts = products
.GroupBy(x => x.Price)
.OrderBy(x => x.Key)
.FirstOrDefault()?
.ToArray() ?? Array.Empty<Product>();
return bestProducts.Count() == 1 ? bestProducts.Single() : null;
}
Upvotes: 0
Reputation: 899
result = Products.GroupBy(x => x.Price)
.Select(g => new { g.Name, Count = g.Count()
})
.Orderby(s.Count)
.Select(x.Name, x.Count).FirstOrDefault();
if(result.Count == 1){
return result;
}
else{
return null;
}
Upvotes: 0
Reputation: 86
This is one way if you want to do it in a single query:
Product result = uniqProductList
.GroupBy(x => x.Price)
.OrderBy(x => x.Key)
.Take(1)
.FirstOrDefault(x => x.Count() == 1)?
.FirstOrDefault();
There's almost certainly some other method that is faster, though.
Upvotes: 3