Nicola Lepetit
Nicola Lepetit

Reputation: 795

Linq query to return the cheapest product, only if it is unique

I have a list of Products, 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

Answers (7)

Rasool Ghorbani
Rasool Ghorbani

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

Dmitrii Bychenko
Dmitrii Bychenko

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 Products 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

Manuel Fabbri
Manuel Fabbri

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

StepUp
StepUp

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

smolchanovsky
smolchanovsky

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

Mehrnoosh
Mehrnoosh

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

M. Flansmose
M. Flansmose

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();
  • Group the results by price
  • Order by price so that the cheapest is the first result
  • Take the first result, since we aren't interested in the other ones
  • Return the grouping if there is only one result in the group
  • Return the value

There's almost certainly some other method that is faster, though.

Upvotes: 3

Related Questions