【问题标题】:List.except on custom classList.except 自定义类
【发布时间】:2017-12-20 03:46:41
【问题描述】:

假设我有一个自定义类:

public class WineCellar
{

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

假设我现在有一个这个自定义类的列表:

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

包含这些项目:

2012 Chianti 12

2011 Chianti 6

2012 Chardonay 12

2011 Chardonay 6

我知道,如果我想比较两个列表并返回一个新列表,其中只有不在另一个列表中的项目,我会这样做:

var newlist = list1.Except(list2);

如何将其扩展到自定义类?假设我有:

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

我想退货:

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

这个伪代码显然不起作用,但希望能说明我正在尝试做的事情。这应该返回一个包含以下项目的自定义类酒窖的列表:

2012 年基安蒂 12

2011 年基安蒂 6

谢谢。

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    您并不想在这里使用Except,因为您没有WineCellar 对象的集合可用作黑名单。你所拥有的是一组规则:“我不想要具有某某酒名的对象”。

    因此最好直接使用Where

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

    以人类可读的形式:

    我想要所有葡萄酒名称不在例外列表中的 WineCellars。

    顺便说一句,WineCellar 类名有点误导;这些物品不是地窖,它们是库存物品。

    【讨论】:

    • 如果您非常频繁地执行此排序或异常列表包含许多项目,您可能希望将异常列表存储为HashSet&lt;string&gt; 以使Contains 函数更快。但是,与往常一样,将这两种方法作为HashSet 的基准测试确实具有不可忽略的创建成本。
    • @Jon,不错的一个...我在这里想得太远了...请注意List&lt;WineCellar&gt;的演员表;-)
    • @DarenThomas:是的,我错过了——现在修复了。
    • @ScottChamberlain:确实如此。我没有走那么远,因为如果这最终会变得非常繁重,那么直接List 对于输入数据也是一个糟糕的选择,因此结果与原始问题没有太多共同之处。
    • 每次我在数组I get flashbacks 上看到使用Contains 的问题时。我只是想帮助其他人从我的错误中吸取教训:)
    【解决方案2】:

    一种解决方案是使用扩展方法:

    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;
                }
            }
        }
    }
    

    然后像这样使用它:

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

    【讨论】:

    • 不错的扩展方法!我唯一会做的不同是将第一个参数更改为this IEnumerable&lt;WineCellar&gt; cellar
    • 使用包含到列表会导致非常糟糕的性能,因为包含在列表上是 O(n) 操作,并且在每一步都执行。在这种情况下,它应该通过 HashSet 或 Dictionary 来完成。
    • @gleb.kudr 我知道。 except,这是一个不同的问题(如何优化运行缓慢的代码)并且取决于输入。当然。继续将wines 参数存储在HashSet&lt;string&gt; 中。
    • 最好的方法是实现GetHashCode。那种“this List”扩展会搞乱泛型的核心概念,而泛型的实现是通用的。
    【解决方案3】:

    exceptionWineListstring[],但 originalWineCellarList&lt;WineCellar&gt;WineCellar 不是 string,因此在它们之间执行 Except 是没有意义的。

    你也可以轻松做到,

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

    我认为您在问题中所暗示的内容类似于

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

    这使您可以将WineCellars 等同于strings。


    但是,如果我要重新设计你的模型,我会想出类似的东西,

    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> 
    {
        ...
    }
    

    然后,您可以看到有几种方法可以比较Wine,我可能想在Wine 的一个或多个属性上过滤Cellar。因此,我可能会想给自己一些灵活性,

    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;
        }
    }
    

    这可能看起来很费力,但它有一些用处。假设我们想要你地窖里除了红里奥哈以外的所有葡萄酒,你可以这样做,

    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));
    

    【讨论】:

      【解决方案4】:

      我遇到了同样的问题。我尝试了 Darren 的示例,但无法正常工作。

      因此,我对 Darren 的示例进行了如下修改:

      static class Helper
      {
          public static IEnumerable<Product> Except(this List<Product> x, List<Product> y)
          {
              foreach(var xi in x)
              {
                  bool found = false;
                  foreach (var yi in y) { if(xi.Name == yi.Name) { found = true; } }
                  if(!found) { yield return xi; }
              }
          }
      }
      

      这对我有用。如果需要,您可以在 if 子句中添加多个字段。

      【讨论】:

        【解决方案5】:

        要在泛型类中直接使用此类扩展方法,您应该实现比较器。它由两个方法组成:Equal 和 GetHashCode。你应该在你的 WineCellar 类中实现它们。 Note the second example

        请注意,基于哈希的方法比基本的“List.Contains...”实现要快得多。

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-25
        • 1970-01-01
        • 1970-01-01
        • 2012-03-10
        • 2013-08-03
        相关资源
        最近更新 更多