C# 判断列表是否有重复项

97

需求: 在一个未排序的列表中,确定是否存在重复项。

我通常会使用n平方的嵌套循环来解决这个问题。我想知道其他人是如何解决这个问题的。在Linq中是否有一种优雅、高性能的方法?最好是可以接受lambda或比较器的通用方法。

注意: 这与LINQ查找列表中的重复项不同,后者返回实际的重复项。我只需要知道是否存在重复项。


1
我记得以前在这里看到过这个问题,人们建议一些巧妙的技巧,但我不记得是什么了...等一下...Jon Skeet在附近。 - Peter Perháč
1
你的问题似乎已经得到了回答,你应该相应地标记它,如果不满意,你可以编辑你的问题以更清楚地解释。 ;) - Trinidad
11个回答

215

除非我漏掉了什么,否则您应该可以通过使用Distinct()来简单地解决问题。尽管它可能不是您能想到的最复杂的实现,但它将告诉您是否删除了任何重复项:

var list = new List<string>();

// Fill the list

if(list.Count != list.Distinct().Count())
{
     // Duplicates exist
}

8
如果我记得正确的话,“Distinct()”在内部使用哈希表,因此时间复杂度应该是O(n)。 - BrokenGlass
4
不要调用list.Count()方法,而应该使用Count属性。我知道LINQ是经过优化的,它会在内部使用它,但我认为最好使用属性。 - Petar Petrov
1
这实际上是我的第一个想法。感谢BrokenGlass确认Distinct()是O(n)。 - kakridge
1
@PetarPetrov - 关于.Count.Count(),我需要使用.Count()。如果不这样做,那么我会得到一个错误,指出“运算符'!='不能应用于类型为'method group'和'method group'的操作数”。 - Vincent Saelzler
3
这个解决方案似乎不够快,因为你要访问List 3次。我建议将元素添加到HasSet中,直到返回false。 - Jean-Charbel VANNIER
显示剩余5条评论

83
根据Eric White的一篇关于如何使用LINQ查找重复项的文章:Find Duplicates using LINQ:
一个简单的方法是编写一个按标识符分组的查询,然后过滤出具有多个成员的组。在下面的示例中,我们想知道4和3是重复的:
int[] listOfItems = new[] { 4, 2, 3, 1, 6, 4, 3 };
var duplicates = listOfItems
    .GroupBy(i => i)
    .Where(g => g.Count() > 1)
    .Select(g => g.Key);
foreach (var d in duplicates)
    Console.WriteLine(d); // 4,3

4
这肯定会奏效,但需要的时间比必要的时间更长(原文作者只需要知道是否存在重复项,而不需要知道这些重复值是什么)。 - Justin Niessner
12
如果您需要知道重复值的情况,这将更有帮助。 - liang

34
为了在列表中存在重复项时允许短路,您可以添加一个HashSet<T>并检查其.Add方法的返回值。
通过使用.Any,您可以在找到重复项时立即短路枚举。
以下是C#和VB中的LINQ扩展方法:
public static bool ContainsDuplicates<T>(this IEnumerable<T> enumerable)
{
    var knownKeys = new HashSet<T>();
    return enumerable.Any(item => !knownKeys.Add(item));
}

Visual Basic:

<Extension>
Public Function ContainsDuplicates(Of T)(ByVal enumerable As IEnumerable(Of T)) As Boolean
    Dim knownKeys As New HashSet(Of T)
    Return enumerable.Any(Function(item) Not knownKeys.Add(item))
End Function

注意: 要检查是否没有重复项,只需将Any更改为All


2
这很优雅,类似于此处描述的方法(https://dev59.com/LmMl5IYBdhLWcg3wP1LE#19476092),也会返回重复项。 - Drew Noakes
为什么这比使用Distinct().Count更好? - Mihai Socaciu
2
@MihaiSocaciu,因为这个短路计算,意味着一旦遇到符合条件的元素,它就不必检查可能非常大的集合中的每个元素。 - KyleMit

15

将所有项放入一个集合中,如果集合的数量与列表的数量不同,则存在重复项。

bool hasDuplicates<T>(List<T> myList) {
    var hs = new HashSet<T>();

    for (var i = 0; i < myList.Count; ++i) {
        if (!hs.Add(myList[i])) return true;
    }
    return false;
}

由于无需遍历整个列表,因此应该比Distinct更高效。


5
不要调用list.Count()方法,改用Count属性。我知道LINQ已经进行了优化并且会在内部使用它,但我认为使用属性更好。 - Petar Petrov
3
假设有重复项,那么使用这种方法会更有效率。但如果没有重复项,那么执行的工作量就相同了。使用哪种方法可能取决于是否存在“正常”情况下没有重复项。 - Jim Mischel
1
@Petar Petrov:好观点。可能应该只使用foreach。并将参数更改为IEnumerable<T>而不是List<T> - Jim Mischel

7
您可以使用IEnumerable.GroupBy方法。
var list = new List<string> {"1", "2","3", "1", "2"};
var hasDuplicates = list.GroupBy(x => x).Any(x => x.Skip(1).Any());

3

类似这样的操作相对简单,能为您提供重复项的计数。

var something = new List<string>() { "One", "One", "Two", "Three" };

var dictionary = new Dictionary<string, int>();

something.ForEach(s =>
    {
        if (dictionary.ContainsKey(s))
        {
            dictionary[s]++;
        }
        else
        {
            dictionary[s] = 1;
        }
    });

我想这与Distinct的实现类似,尽管我不确定。

2
使用HashSet似乎更加直观易用。 - Trinidad
1
是的,那样更有意义。 - Ian P
@Trinidad:但是不会给你一个计数。 - recursive
@recursive,那不是问题的一部分。请参见:_在未排序的列表中确定是否存在重复项_。 - Trinidad
这太完美了,因为我刚接触C#,需要跟踪一组值中每个实例的计数(例如从http资源中提取的20,000多个文件名),并且我想知道在潜在地覆盖具有重复文件名的文件之前是否存在任何重复项。我考虑使用字典,所以很高兴在这里看到它被推荐。 - Michael M

1
使用 Enumerable.AnyHashSet.Add 配合使用,例如:
List<string> list = new List<string> {"A", "A", "B", "C", "D"};
HashSet<string> hashSet = new HashSet<string>();
if(list.Any(r => !hashSet.Add(r)))
{
   //duplicate exists. 
}

如果项已经存在于HashSet中,HashSet.Add将返回false。这不会迭代整个列表。

1
你可以使用IEnumerable的Distinct()扩展方法。

1

如果你使用整数或者有序集合,可以使用二叉树来获得O(nlog n)的性能。

或者,找到另一种更快的排序方式,然后简单地检查每个值是否与前一个值不同。


0
你可以使用 Distinct() 语句来查找唯一的记录。然后像这样与原始的泛型列表进行比较:
  if (dgCoil.ItemsSource.Cast<BLL.Coil>().ToList().Count != dgCoil.ItemsSource.Cast<BLL.Coil>().Select(c => c.CoilNo).Distinct().Count())
  {    
    //Duplicate detected !!
    return;
  }

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