【问题标题】:LINQ query to detect duplicate properties in a list of objectsLINQ 查询以检测对象列表中的重复属性
【发布时间】:2009-12-16 10:41:58
【问题描述】:

我有一个对象列表。这些对象由一个自定义类组成,该类基本上包含两个字符串字段String1String2

我需要知道的是,这些字符串中的任何一个是否在该列表中重复。所以我想知道是objectA.String1 == objectB.String1,还是ObjectA.String2 == ObjectB.String2,还是ObjectA.String1 == ObjectB.String",还是ObjectA.String2 == ObjectB.String1

另外,我想将每个包含重复字符串的对象标记为具有重复字符串(对象上带有 bool HasDuplicate)。

所以当重复检测运行时,我想简单地遍历列表,如下所示:

foreach (var item in duplicationList)
    if (item.HasDuplicate)
        Console.WriteLine("Duplicate detected!");

这似乎是一个用 LINQ 解决的好问题,但我终其一生都无法找到一个好的查询。所以我已经使用'good-old' foreach 解决了它,但我仍然对 LINQ 版本感兴趣。

【问题讨论】:

    标签: c# .net linq linq-to-objects


    【解决方案1】:

    这是一个完整的代码示例,应该适用于您的情况。

    class A
    {
        public string Foo   { get; set; }
        public string Bar   { get; set; }
        public bool HasDupe { get; set; }
    }
    
    var list = new List<A> 
              { 
                  new A{ Foo="abc", Bar="xyz"}, 
                  new A{ Foo="def", Bar="ghi"}, 
                  new A{ Foo="123", Bar="abc"}  
              };
    
    var dupes = list.Where(a => list
              .Except(new List<A>{a})
              .Any(x => x.Foo == a.Foo || x.Bar == a.Bar || x.Foo == a.Bar || x.Bar == a.Foo))
              .ToList();
    
    dupes.ForEach(a => a.HasDupe = true);
    

    【讨论】:

    • LINQPad 是解决此类问题的绝佳工具 - 每个 C# 开发人员都应该拥有一份副本。
    • 不错。只有一点 - 我认为将逻辑从 except 移动到 Any 方法会更有效率,因为它将为每个被检查的元素保存一个 List 的创建,例如var dupes = list.Where( a => list .Any(a != x && (x => x.Foo == a.Foo || x.Bar == a.Bar || x.Foo == a. Bar || x.Bar == a.Foo)) ).ToList();
    • 请记住,空字符串也彼此相等,因此如果您想忽略空字符串,请对其进行测试。
    • 请删除 ForEach 扩展方法。它被气馁,不再存在。
    【解决方案2】:

    这应该可行:

    public class Foo
    {
        public string Bar;
        public string Baz;
        public bool HasDuplicates;
    }
    
    public static void SetHasDuplicate(IEnumerable<Foo> foos)
    {
        var dupes = foos
            .SelectMany(f => new[] { new { Foo = f, Str = f.Bar }, new { Foo = f, Str = f.Baz } })
            .Distinct() // Eliminates double entries where Foo.Bar == Foo.Baz
            .GroupBy(x => x.Str)
            .Where(g => g.Count() > 1)
            .SelectMany(g => g.Select(x => x.Foo))
            .Distinct()
            .ToList();
    
        dupes.ForEach(d => d.HasDuplicates = true);    
    }
    

    你基本上在做的是

    1. SelectMany : 创建一个包含所有字符串的列表,并附上它们的 Foo
    2. 不同:删除同一 Foo 实例的双条目 (Foo.Bar == Foo.Baz)
    3. GroupBy : 按字符串分组
    4. Where :过滤包含多个项目的组。这些包含重复项。
    5. SelectMany : 从组中取回 foos。
    6. 不同:从列表中删除两次出现的 foo。
    7. ForEach :设置 HasDuplicates 属性。

    与 Winston Smith 的解决方案相比,此解决方案的一些优势是:

    1. 更容易扩展到更多的字符串属性。假设有 5 个属性。在他的解决方案中,您必须编写 125 个比较来检查重复项(在 Any 子句中)。在此解决方案中,只需在第一个 selectmany 调用中添加属性即可。
    2. 大型列表的性能应该更好。 Winston 的解决方案为列表中的每个项目迭代列表,而此解决方案只迭代一次。 (温斯顿的解是 O(n²),而这个是 O(n))。

    【讨论】:

    • Grouping lazy 是否评估其组成员? g.Skip(1).Any() 可能是对 g.Count() > 1 的改进
    • @Jimmy 在这种情况下并不重要,因为这些组不会被懒惰地评估。不过,我确实喜欢 Skip(1).Any() 技巧。对于我自己的项目,我总是有扩展方法 CountIs(int expected)、CountIsGreaterThan(int expected)... 一旦知道答案就会停止评估。
    【解决方案3】:

    首先,如果您的对象还没有 HasDuplicate 属性,请声明一个实现 HasDuplicateProperties 的扩展方法:

    public static bool HasDuplicateProperties<T>(this T instance)
        where T : SomeClass 
        // where is optional, but might be useful when you want to enforce
        // a base class/interface
    {
        // use reflection or something else to determine wether this instance
        // has duplicate properties
        return false;
    }
    

    您可以在查询中使用该扩展方法:

    var itemsWithDuplicates = from item in duplicationList
                              where item.HasDuplicateProperties()
                              select item;
    

    同样适用于普通属性:

    var itemsWithDuplicates = from item in duplicationList
                              where item.HasDuplicate
                              select item;
    

    var itemsWithDuplicates = duplicationList.Where(x => x.HasDuplicateProperties());
    

    【讨论】:

    • 这不是我的问题。我想知道如何确定何时有重复项,以便设置布尔值。设置 bool 后,我知道如何从设置了它的列表中获取所有对象。
    【解决方案4】:

    帽子提示https://stackoverflow.com/a/807816/492

    var duplicates = duplicationList
                    .GroupBy(l => l)
                    .Where(g => g.Count() > 1)
                    .Select(g => {foreach (var x in g)
                                     {x.HasDuplicate = true;}
                                 return g;
                    });
    

    duplicates 是一次性的,但它可以让你在更少的枚举中到达那里。

    【讨论】:

      【解决方案5】:
      var dups = duplicationList.GroupBy(x => x).Where(y => y.Count() > 1).Select(y => y.Key);
      
      foreach (var d in dups)
          Console.WriteLine(d);
      

      【讨论】:

      • 我已经使用以下程序在 LINQPad 中测试了你的代码: void Main() { var duplicationList = new List { new TestObject("1", "2"), new TestObject( "3", "4"), 新的 TestObject("1", "6") }; var dups = duplicationList.GroupBy(x => x).Where(y => y.Count() > 1).Select(y => y.Key); dups.Dump("重复转储:" + dups.Count()); } 公共类 TestObject { 公共 TestObject(字符串 s1,字符串 s2) { String1 = s1;字符串2 = s2; IsDuplicate = 假; } 公共字符串 String1;公共字符串 String2;公共布尔 IsDuplicate;它不起作用。 dups 包含 0 个值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-02-12
      • 2012-01-22
      • 1970-01-01
      • 1970-01-01
      • 2018-01-05
      • 1970-01-01
      • 2019-03-15
      相关资源
      最近更新 更多