在一个对象列表中检测重复属性的LINQ查询

4
我有一个对象列表。这些对象由一个自定义类组成,该类基本上包含两个字符串字段String1String2
我需要知道的是,这些字符串中是否有任何重复项。因此,我想知道objectA.String1 == objectB.String1,或ObjectA.String2 == ObjectB.String2,或ObjectA.String1 == ObjectB.String,或ObjectA.String2 == ObjectB.String1
此外,我想将包含重复字符串的每个对象标记为具有重复字符串(在对象上使用bool HasDuplicate)。
因此,当检测到重复项时,我只需对列表进行foreach操作,如下所示:
foreach (var item in duplicationList)
    if (item.HasDuplicate)
        Console.WriteLine("Duplicate detected!");

这似乎是一个可以用LINQ解决的好问题,但是我无论如何都想不出一个好的查询。所以我使用了“老派”的foreach解决了它,但我仍然对LINQ版本感兴趣。

5个回答

12

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

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

2
LINQPad是一个解决此类问题的绝佳工具,每个C#开发人员都应该拥有一份副本。 - Winston Smith
不错。只有一个要点 - 我认为将逻辑从Except方法移动到Any方法中会更有效率,因为它将节省为每个被检查的元素创建一个列表,例如。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();
- Mike Goatly
请注意空字符串也互相等同,因此如果您想忽略空字符串,请进行测试。 - CAD bloke
请移除ForEach扩展方法。它已经被弃用并且不再存在。 - SuperJMN

5
这应该可以运行:
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. Distinct:删除相同Foo实例的重复条目(Foo.Bar == Foo.Baz)
  3. GroupBy:按字符串分组
  4. Where:过滤其中有多个项目的组。这些包含重复项。
  5. SelectMany:从组中获取foos。
  6. Distinct:从列表中删除foo的双重出现。
  7. ForEach:设置HasDuplicates属性。
与Winston Smith的解决方案相比,这种解决方案的一些优点是:
  1. 更容易扩展到更多字符串属性。假设有5个属性。在他的解决方案中,您需要编写125个比较来检查重复项(在Any子句中)。在此解决方案中,只需在第一个SelectMany调用中添加属性即可。
  2. 对于大型列表,性能应该更好。 Winston的解决方案对列表进行了迭代,每个项都要迭代一次,而此解决方案仅对其进行了一次迭代。(Winston的解决方案为O(n²),而此解决方案为O(n))。

分组是否惰性地评估其组成员?g.Skip(1).Any()可能比g.Count() > 1更好 - Jimmy
@Jimmy 在这种情况下,这并不重要,因为组没有惰性评估。我确实喜欢Skip(1).Any()的技巧。对于我的项目,我总是有扩展方法CountIs(int expected),CountIsGreaterThan(int expected)...,它们一旦知道答案就停止评估。 - Geert Baeyaert

0
致敬https://dev59.com/QXRA5IYBdhLWcg3w1BqW#807816
var duplicates = duplicationList
                .GroupBy(l => l)
                .Where(g => g.Count() > 1)
                .Select(g => {foreach (var x in g)
                                 {x.HasDuplicate = true;}
                             return g;
                });

duplicates 是一个临时变量,但它可以在较少的枚举中帮助你实现目标。


0
首先,如果您的对象尚未具有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());

这不是我的问题。我想知道如何确定何时有重复项,以便我可以设置布尔值。当布尔值被设置时,我知道如何从列表中获取所有具有该值的对象。 - Jeroen-bart Engelen

-2
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<TestObject> { new TestObject("1", "2"), new TestObject("3", "4"), new TestObject("1", "6") }; var dups = duplicationList.GroupBy(x => x).Where(y => y.Count() > 1).Select(y => y.Key); dups.Dump("Duplicate dump: " + dups.Count()); }public class TestObject { public TestObject(string s1, string s2) { String1 = s1; String2 = s2; IsDuplicate = false; } public string String1; public string String2; public bool IsDuplicate; }但是它没有起作用。dups不包含任何值。 - Jeroen-bart Engelen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接