LINQ 查询以检测对象列表中的重复属性

Posted

技术标签:

【中文标题】LINQ 查询以检测对象列表中的重复属性【英文标题】:LINQ query to detect duplicate properties in a list of objects 【发布时间】: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 版本感兴趣。

【问题讨论】:

【参考方案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);    

你基本上在做的是

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

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

    更容易扩展到更多的字符串属性。假设有 5 个属性。在他的解决方案中,您必须编写 125 个比较来检查重复项(在 Any 子句中)。在此解决方案中,只需在第一个 selectmany 调用中添加属性即可。 大型列表的性能应该更好。 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://***.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 个值。

以上是关于LINQ 查询以检测对象列表中的重复属性的主要内容,如果未能解决你的问题,请参考以下文章

Linq 查询 - 列表中的列表 [重复]

使用Linq获取列表中对象的索引[重复]

删除列表中的重复对象 (C#)

如何使用 linq 查询具有嵌套列表的对象?

C#使用列表属性展平对象列表[重复]

C# LINQ 组按属性集合,然后按列表定义的显式顺序对每个组进行排序[重复]