LINQ 查询以检测对象列表中的重复属性
Posted
技术标签:
【中文标题】LINQ 查询以检测对象列表中的重复属性【英文标题】:LINQ query to detect duplicate properties in a list of objects 【发布时间】:2009-12-16 10:41:58 【问题描述】:我有一个对象列表。这些对象由一个自定义类组成,该类基本上包含两个字符串字段String1
和String2
。
我需要知道的是,这些字符串中的任何一个是否在该列表中重复。所以我想知道是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以上是关于LINQ 查询以检测对象列表中的重复属性的主要内容,如果未能解决你的问题,请参考以下文章