在C#中检查包含空值的列表是否有重复项

32
在C#中,我可以使用类似以下的语句:
List<string> myList = new List<string>();

if (myList.Count != myList.Distinct().Count())
{
    // there are duplicates
}

在检查列表中是否有重复元素时,如果列表中存在null项,则会产生误报。我可以使用一些笨重的代码来完成此操作,但是否有一种简洁的方法来检查列表中的重复项并忽略空值?

5个回答

56
如果你担心性能问题,以下代码会在找到第一个重复项时立即停止 - 所有其他解决方案至少需要迭代整个输入一次。
var hashset = new HashSet<string>();
if (myList.Where(s => s != null).Any(s => !hashset.Add(s)))
{
    // there are duplicates
}

hashset.Add 如果集合中已经存在该项,则返回 false,而 Any 在第一个 true 值出现时即返回 true,因此这将仅在遇到第一个重复项时停止搜索输入。


我以为 GroupBy 是延迟执行的? - Jodrell
2
@Jodrell 直到您尝试读取第一组时,它才被延迟,此时它会读取整个输入序列,对其进行分组,并返回第一组。 - Rawling
在这种情况下,需要使用 HashSet - Jodrell
1
+1 这将为非常大的集合提供最佳性能。但对于小集合来说,由于创建/更新/重新分配HashSet的开销,不一定是最优选择。 - Joe
2
@Joe Distinct 在底层实际上使用了 HashSet 来完成相同的操作,而 GroupBy 也会执行类似的操作。 - Rawling
显示剩余2条评论

32

我会以不同的方式处理:

由于Linq语句将被惰性评估,.Any会短路 - 这意味着如果有重复项,您不需要迭代和计算整个列表 - 因此应该更有效率。

var dupes = myList
    .Where(item => item != null)
    .GroupBy(item => item)
    .Any(g => g.Count() > 1);

if(dupes)
{
    //there are duplicates
}

编辑:http://pastebin.com/b9reVaJu一些Linqpad基准测试,似乎得出GroupByCount()更快。

编辑2:下面Rawling的答案似乎比这种方法快至少5倍!


5
Any 仍需要至少一次迭代来构建分组,以及第二次迭代来检查分组的长度(尽管重新考虑后,也许可以通过短路计数而不进行迭代),然而可以通过最多一次对整个输入进行迭代来执行此检查。 - Rawling
@Rawling 我假设由于Count()作为“Any”的一部分被评估,它会懒惰地评估分组 - 我们如何测试这一点?这非常有趣! - Dave Bish
1
你可以使用 ILSpy 来查找 GroupBy 的实现,但是先思考一下 - 在 GroupBy 返回第一组之前,它需要先消耗整个输入序列以确保它拥有所有将进入该组的项。 - Rawling
@svick 我的意思是,由于 GroupBy 将所有项读入组中,我希望在组上使用 Count() 时能够像在列表上使用 Count() 一样,返回一个现有的 int,而不是通过迭代所有项进行计数。 "短路" 不是最好的词汇选择 :) - Rawling
1
@Jodrell 我会期望立即执行,因为DistinctGroupBy都需要构建某种值的哈希表。 - Rawling
显示剩余9条评论

11
var nonNulls = myList.Where(x => x != null)
if (nonNulls.Count() != nonNulls.Distinct().Count())
{
    // there are duplicates
}

1
这会使列表被迭代两次。 - jk.

4

两个空值是重复的,对吧?

无论如何,比较不含空值的列表:

var denullified = myList.Where(l => l != null);
if(denullified.Count() != denullified.Distinct().Count()) ...

3
我可以想到很多情况下不应该将null视为重复项。我们有一个网站,用户可以通过电子邮件或(可选)用户名登录。没有用户名的用户的用户名为null,但是任何用户名都不能重复。 - xdumaine

1
编辑:我的第一次尝试很糟糕,因为它没有延迟执行。
取而代之,
var duplicates = myList
    .Where(item => item != null)
    .GroupBy(item => item)
    .Any(g => g.Skip(1).Any());

删除了较差的实现。

(原文保留HTML格式)

@svick,然而,这还不够懒。 - Jodrell

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