Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ Sorting - First three need to be different manufacturers

Tags:

c#

sorting

linq

My OM has a 'product' object.
Each product has a 'manufacturer id' (property, integer).
When I have a list of products to display, the first three are displayed as the 'featured products'.
The list is already sorted in a specific sort order, putting the 'featured' products first in the list.

However, I now need to ensure the featured products in the listing are from different Manufacturers. I want to have a method to call to do this re-sorting. Trying to utilize LINQ to to the querying of the input 'products' and the 'results'

public List<Product> SetFeatures(List<Product> products, int numberOfFeatures)
{
    List<Product> result;

    // ensure the 2nd product is different manufacturer than the first ....

    // ensure the 3rd product is a different manufacturer than the first two... 

    // ... etc ... for the numberOfFeatures

    return result;

}

Thanks in advance.

Clarification:
The original list is in a specific order: the 'best selling', highest first (descending order). The resulting list should remain in this order with the exception of adjusting or moving 'up' items so that the differing manufacturers are seen the top n features.

If the first n (numberOfFeatures) of items all have different manufacturers, then the listing does not need to be altered at all.
e.g. If numberOfFeatures = 3
Product 1 - Manufacturer A (1st feature)
Product 2 - Manufacturer B (2nd feature)
Product 3 - Manufacturer C (3rd feature)
Product 4 - Manufacturer A (...not checked...)
Product 5 - Manufacturer A (...not checked...)

e.g. Case to adjust ... for example ... INPUT List
Product 1 - Manufacturer A
Product 2 - Manufacturer A
Product 3 - Manufacturer B
Product 4 - Manufacturer A
Product 5 - Manufacturer F
(... we would want ...)
Product 1 - Manufacturer A (1st feature)
Product 3 - Manufacturer B (2nd feature ... moved up)
Product 5 - Manufacturer F (3rd feature ... moved up)
Product 2 - Manufacturer A (...pushed down the list...)
Product 4 - Manufacturer A (...pushed down the list...)

like image 323
Rob Avatar asked Mar 19 '10 04:03

Rob


3 Answers

I think Bryan encapsulated the sorting logic pretty well and it's not something I thought of doing. I'd like to present my take on it anyway using a Foo example.

        List<Foo> foos = new List<Foo>()
    {
        new Foo() { Baz = 1, Blah = "A"},
        new Foo() { Baz = 2, Blah = "A"},
        new Foo() { Baz = 3, Blah = "B"},
        new Foo() { Baz = 4, Blah = "B"},
        new Foo() { Baz = 5, Blah = "B"},
        new Foo() { Baz = 6, Blah = "C"},
        new Foo() { Baz = 7, Blah = "C"},
        new Foo() { Baz = 8, Blah = "D"},
        new Foo() { Baz = 9, Blah = "A"},
        new Foo() { Baz = 10, Blah = "B"},
    };

    var query = foos.Distinct(new FooComparer()).Take(3).ToList();
    var theRest = foos.Except(query);

    query.AddRange(theRest);

FooComparer being

    public class FooComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        return x.Blah == y.Blah;
    }

    public int GetHashCode(Foo obj)
    {
        return obj.Blah.GetHashCode();
    }
}

You get (Baz) 1, 3, and 6 shifted to top, the remaining in their original order afterwards.

like image 100
Anthony Pegram Avatar answered Oct 10 '22 11:10

Anthony Pegram


    public List SetFeatures(List products, int numberOfFeatures)
    {
        var manufacturerProducts =
            from product in products
            group product by product.ManufacturerId into productGroup
            select productGroup.First();

        return manufacturerProducts.Take(numberOfFeatures).ToList();
    }

Edit: The question is really about a custom ordering of a list. I chose to describe the comparison itself, and use that to sort:

return products
    .OrderBy(product => product, new FeaturedProductComparer(numberOfFeatures))
    .ToList();

This is done by implementing IComparer<Product> and keeping tracking of the manufacturers that have been encountered. When there are less than 3 and we find a new one, we favor that product:

private class FeaturedProductComparer : IComparer<Product>
{
    // OrderBy preserves the order of equal elements
    private const int _originalOrder = 0;
    private const int _xFirst = -1;
    private const int _yFirst = 1;

    private readonly HashSet<int> _manufacturerIds = new HashSet<int>();
    private readonly int _numberOfFeatures;

    internal FeaturedProductComparer(int numberOfFeatures)
    {
        _numberOfFeatures = numberOfFeatures;
    }

    public int Compare(Product x, Product y)
    {
        return _manufacturerIds.Count == _numberOfFeatures
            ? _originalOrder
            : CompareManufacturer(x, y);
    }

    private int CompareManufacturer(Product x, Product y)
    {
        if(!_manufacturerIds.Contains(x.ManufacturerId))
        {
            _manufacturerIds.Add(x.ManufacturerId);

            // Sort existing featured products ahead of new ones
            return _manufacturerIds.Contains(y.ManufacturerId) ? _yFirst : _xFirst;
        }
        else if(!_manufacturerIds.Contains(y.ManufacturerId))
        {
            _manufacturerIds.Add(y.ManufacturerId);

            // Sort existing featured products ahead of new ones
            return _manufacturerIds.Contains(x.ManufacturerId) ? _xFirst : _yFirst;
        }
        else
        {
            return _originalOrder;
        }
    }
}
like image 2
Bryan Watts Avatar answered Oct 10 '22 10:10

Bryan Watts


Edit turns out Distinct() is not required, the revised code:

Is this what you want?

var result = 
        products
            .GroupBy(x => x.Id)
            .Take(numberOfFeatures)
            .Select(x => x.First())
            .Union(products);
return result.ToList();

Note that GroupBy will have the correct sequence and Union will also

like image 1
Cornelius Avatar answered Oct 10 '22 09:10

Cornelius