Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List.except on custom class

Tags:

c#

linq

lets say I have a custom class:

public class WineCellar
{

    public string year;
    public string wine;
    public double nrbottles;
}

Lets say I now have a List of this custom class:

List<WineCellar> orignialwinecellar = List<WineCellar>();

containing these items:

2012 Chianti 12

2011 Chianti 6

2012 Chardonay 12

2011 Chardonay 6

I know that if I want to compare two list and return a new list that has only items that are not in the other list I would do:

var newlist = list1.Except(list2);

How can I extend this to a custom class? Lets say I have:

string[] exceptionwinelist = {"Chardonay", "Riesling"};

I would like this to be returned:

List<WineCellar> result = originalwinecellar.wine.Except(exceptionwinelist);

This pseudocode obviously doesnt work but hopefully illustrates what I m trying to do. This shoudl then return a List of the custom class winecellar with following items:

2012 Chianti 12

2011 Chianti 6

Thanks.

like image 980
nik Avatar asked Jul 22 '13 08:07

nik


People also ask

How do you use list except?

Get the difference between two arrays using the Except() method. The following are the two arrays. int[] arr = { 9, 12, 15, 20, 35, 40, 55, 67, 88, 92 }; int[] arr2 = { 20, 35 }; To get the difference, use Except() method that returns the first list, except the elements in the second list.

How to use Except in LINQ c#?

LINQ: ExceptThe Except() method requires two collections. It returns a new collection with elements from the first collection which do not exist in the second collection (parameter collection). Except extension method doesn't return the correct result for the collection of complex types.

Where and Except LINQ c#?

What is LINQ Except in C#? The LINQ Except Method in C# is used to return the elements which are present in the first data source but not in the second data source. There are two overloaded versions available for the LINQ Except Method as shown below.


3 Answers

You don't really want to use Except here, as you don't have a collection of WineCellar objects to use as a blacklist. What you have is a collection of rules: "I don't want objects with such and such wine names".

Therefore it's better to simply use Where:

List<WineCellar> result = originalwinecellar
    .Where(w => !exceptionwinelist.Contains(w.wine))
    .ToList();

In human-readable form:

I want all WineCellars where the wine name is not present in the list of exceptions.

As an aside, the WineCellar class name is a bit misleading; those objects are not cellars, they are inventory items.

like image 100
Jon Avatar answered Oct 17 '22 10:10

Jon


One solution is with an extension method:

public static class WineCellarExtensions
{
    public static IEnumerable<WineCellar> Except(this List<WineCellar> cellar, IEnumerable<string> wines)
    {
        foreach (var wineCellar in cellar)
        {
            if (!wines.Contains(wineCellar.wine))
            {
                yield return wineCellar;
            }
        }
    }
}

And then use it like this:

List<WineCellar> result = originalwinecellar.Except(exceptionwinelist).ToList();
like image 36
Daren Thomas Avatar answered Oct 17 '22 11:10

Daren Thomas


exceptionWineList is a string[] but originalWineCellar is a List<WineCellar>, WineCellar is not a string, so it does not make sense to perform an Except between these.

You could just as easily do,

// use HashSet for look up performance.
var exceptionWineSet = new HashSet<string>(exceptionWineList);
var result = orginalWineCellar.Where(w => !exceptionWineSet.Contains(w.Wine));

What I think you are alluding to in your question is something like

WineCellar : IEquatable<string>
{
    ...
    public bool Equals(string other)
    {
        return other.Equals(this.wine, StringComparison.Ordinal);
    }
}

which allows you to equate WineCellars to strings.


However, if I were to rework your model I'd come up with something like,

enum WineColour
{
    Red,
    White,
    Rose
}

enum WineRegion
{
    Bordeaux,
    Rioja,
    Alsace,
    ...
}

enum GrapeVariety
{
    Cabernet Sauvignon,
    Merlot,
    Ugni Blanc,
    Carmenere,
    ...
}

class Wine
{
    public string Name { get; set; }
    public string Vineyard { get; set; }
    public WineColour Colour { get; set; }
    public WineRegion Region { get; set; }
    public GrapeVariety Variety { get; set; }
}

class WineBottle
{
    public Wine Contents { get; set; }
    public int Millilitres { get; set; }
    public int? vintage { get; set; }
}

class Bin : WineBottle
{
    int Number { get; set; }
    int Quantity { get; set; }
}

class Cellar : ICollection<WineBottle> 
{
    ...
}

Then, you can see that there are several ways to compare Wine and I may want to filter a Cellar on one or more of Wine's properties. Therefore I might be temtpted to give myself some flexibility,

class WineComparer : EqualityComparer<Wine>
{
    [Flags]
    public Enum WineComparison
    {
        Name = 1,
        Vineyard= 2,
        Colour = 4,
        Region = 8,
        Variety = 16,
        All = 31
    }

    private readonly WineComparison comparison;

    public WineComparer()
        : this WineComparer(WineComparison.All)
    {
    }

    public WineComparer(WineComparison comparison)
    {
        this.comparison = comparison;
    }

    public override bool Equals(Wine x, Wine y)
    {
        if ((this.comparison & WineComparison.Name) != 0
            && !x.Name.Equals(y.Name))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Vineyard) != 0
            && !x.Vineyard.Equals(y.Vineyard))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Region) != 0
            && !x.Region.Equals(y.Region))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Colour) != 0
            && !x.Colour.Equals(y.Colour))
        {
            return false;
        }

        if ((this.comparison & WineComparison.Variety) != 0
            && !x.Variety.Equals(y.Variety))
        {
            return false;
        }

        return true;
    }

    public override bool GetHashCode(Wine obj)
    {
        var code = 0;
        if ((this.comparison & WineComparison.Name) != 0)
        {
            code = obj.Name.GetHashCode();
        }

        if ((this.comparison & WineComparison.Vineyard) != 0)
        {
            code = (code * 17) + obj.Vineyard.GetHashCode();
        }

        if ((this.comparison & WineComparison.Region) != 0)
        {
            code = (code * 17) + obj.Region.GetHashCode();
        }

        if ((this.comparison & WineComparison.Colour) != 0)
        {
            code = (code * 17) + obj.Colour.GetHashCode();
        }

        if ((this.comparison & WineComparison.Variety) != 0)
        {
            code = (code * 17) + obj.Variety.GetHashCode();
        }

        return code;
    }
}

this probably looks like a lot of effort but it has some use. Lets say we wanted all the wine except the Red Rioja in your cellar, you could do something like,

var comparison = new WineComparer(
    WineComparison.Colour + WineComparison.Region);

var exception = new Wine { Colour = WineColour.Red, Region = WineRegion.Rioja }; 

var allButRedRioja = cellar.Where(c => 
    !comparison.Equals(c.Wine, exception));
like image 2
Jodrell Avatar answered Oct 17 '22 11:10

Jodrell