C# LINQ在List中查找重复项

513
使用LINQ,从一个List<int>中,如何检索包含多个重复条目及其值的列表?
13个回答

863

解决问题最简单的方法是根据元素的值对它们进行分组,然后在组中选择一个代表元素(如果组中有多个元素)。在LINQ中,这可以这样实现:

var query = lst.GroupBy(x => x)
              .Where(g => g.Count() > 1)
              .Select(y => y.Key)
              .ToList();

如果你想知道元素被重复的次数,你可以使用:

var query = lst.GroupBy(x => x)
              .Where(g => g.Count() > 1)
              .Select(y => new { Element = y.Key, Counter = y.Count() })
              .ToList();

这将返回一个匿名类型的List,每个元素都将具有ElementCounter属性,以检索所需信息。

最后,如果您要查找字典,可以使用

var query = lst.GroupBy(x => x)
              .Where(g => g.Count() > 1)
              .ToDictionary(x => x.Key, y => y.Count());
这将返回一个字典,以您的元素作为键,以其重复次数作为值。

现在只是一个疑问,假设重复的整数被分配到n个整数数组中,我使用字典和for循环来确定哪个数组包含重复项,并根据分配逻辑将其删除,是否有更快的方法(linq想法)来实现这个结果?提前感谢您的关注。 - Mirko Arcese
我正在做类似于这样的事情:代码for (int i = 0; i < duplicates.Count; i++) { int duplicate = duplicates[i]; duplicatesLocation.Add(duplicate, new List()); for (int k = 0; k < hitsList.Length; k++) { if (hitsList[k].Contains(duplicate)) { duplicatesLocation.ElementAt(i).Value.Add(k); } } // 根据某些规则删除重复项。 }代码 - Mirko Arcese
如果您想在数组列表中查找重复项,请查看SelectMany。 - Save
我正在搜索一个列表数组中的重复项,但不知道如何使用SelectMany来帮助我解决它。 - Mirko Arcese
7
如果想要检查一个集合是否有多于一个元素,使用Skip(1).Any()比Count()更有效率。想象一下一个包含1000个元素的集合,Skip(1).Any()会在找到第二个元素时便能检测出它有超过1个元素。而使用Count()则需要访问整个集合。 - Harald Coppoolse
显示剩余7条评论

209

查找一个可枚举对象是否包含任何重复项

var anyDuplicate = enumerable.GroupBy(x => x.Key).Any(g => g.Count() > 1);

查找枚举中的所有值是否唯一

var allUnique = enumerable.GroupBy(x => x.Key).All(g => g.Count() == 1);

3
这些变量是否总是布尔类型的相反值呢?在所有情况下,anyDuplicate == !allUnique 是否都成立。 - Garr Godfrey
5
@GarrGodfrey:它们总是布尔值的相反。 - Caltor
5
要获取被复制的内容,只需将“Any”改为“Where”。 - Ariwibawa

34

仅查找重复的值:

var duplicates = list.GroupBy(x => x.Key).Where(g => g.Count() > 1);

例如。

var list = new[] {1,2,3,1,4,2};

GroupBy 会按照它们的键分组数字,并将重复次数保留在其中。之后,我们只需检查重复多于一次的值。

要仅查找唯一值:

var unique = list.GroupBy(x => x.Key).Where(g => g.Count() == 1);
例如。
var list = new[] {1,2,3,1,4,2};

GroupBy 会按照它们的键分组数字,并将重复的次数与之保持在一起。之后,我们只检查那些仅重复了一次的值,也就是唯一的值。


以下代码也将找到唯一的项。 var unique = list.Distinct(x => x) - Malu MN
1
你的任何语法都不会返回重复项,它只会告诉你是否有任何重复项。在第一个示例中也使用ALL语法,那应该就可以解决了! - Silviu Preda
2
两个例子都只返回布尔值,这不是 OP 所要求的。 - DarkBarbarian
@MaluMN:答案中使用“unique values only”来表示“仅出现一次的值”。Distinct的作用不同,它不仅会返回仅出现一次的值,还会返回出现多次的值(但只返回一次而不是所有多次出现的值);这与答案所指的不同。 - Flater
.All(g => g.Count() == 1) 应该改为 .Where(g => g.Count() == 1)All 不会像你所说的那样“查找唯一值”,它只是确认整个列表中没有重复项(即所有组的计数都为1)。 - Flater
同样的注释如前所述适用于 .Any(g => g.Count() > 1),这应该是 .Where(g => g.Count() > 1)Any 不会找到重复项本身,它只会确认至少存在一个重复项(即存在一个计数大于 1 的 任何 组)。 - Flater

32

另一种方法是使用 HashSet

var hash = new HashSet<int>();
var duplicates = list.Where(i => !hash.Add(i));

如果您希望在重复列表中仅保留唯一值:

var myhash = new HashSet<int>();
var mylist = new List<int>(){1,1,2,2,3,3,3,4,4,4};
var duplicates = mylist.Where(item => !myhash.Add(item)).Distinct().ToList();

这里是相同解决方案的通用扩展方法:

public static class Extensions
{
  public static IEnumerable<TSource> GetDuplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IEqualityComparer<TKey> comparer)
  {
    var hash = new HashSet<TKey>(comparer);
    return source.Where(item => !hash.Add(selector(item))).ToList();
  }

  public static IEnumerable<TSource> GetDuplicates<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
  {
    return source.GetDuplicates(x => x, comparer);      
  }

  public static IEnumerable<TSource> GetDuplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
  {
    return source.GetDuplicates(selector, null);
  }

  public static IEnumerable<TSource> GetDuplicates<TSource>(this IEnumerable<TSource> source)
  {
    return source.GetDuplicates(x => x, null);
  }
}

这个结果与预期不符。使用List<int> { 1, 2, 3, 4, 5, 2 }作为源,结果是一个IEnumerable<int>,其中只有一个元素的值为1(正确的重复值应该是2)。 - BCA
@BCA昨天,我认为你错了。看看这个例子:https://dotnetfiddle.net/GUnhUl - HuBeZa
你的fiddle打印出了正确的结果。然而,我在其下直接添加了一行代码:Console.WriteLine("Count: {0}", duplicates.Count());,并且它打印了6。除非我对该函数的要求有所遗漏,否则结果集中应该只有1个项目。 - BCA
@BCA 昨天,这是由于LINQ延迟执行引起的错误。 我已经添加了 ToList 以解决此问题,但这意味着该方法在调用时立即执行,而不是在迭代结果时执行。 - HuBeZa
var hash = new HashSet<int>(); var duplicates = list.Where(i => !hash.Add(i)); will lead to a list that includes all occurrences of duplicates. So if you have four occurrences of 2 in your list, then your duplicate list will contain three occurrences of 2, since only one of the 2's can be added to the HashSet. If you want your list to contain unique values for each duplicate, use this code instead: var duplicates = mylist.Where(item => !myhash.Add(item)).ToList().Distinct().ToList(); - solid_luffy
这对于IEnumerable工作得很好,感谢大家,可以从IEnumerable转换为目标类型。 - R.Akhlaghi

14

你可以这样做:

var list = new[] {1,2,3,1,4,2};
var duplicateItems = list.Duplicates();

使用这些扩展方法:

public static class Extensions
{
    public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
    {
        var grouped = source.GroupBy(selector);
        var moreThan1 = grouped.Where(i => i.IsMultiple());
        return moreThan1.SelectMany(i => i);
    }

    public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source)
    {
        return source.Duplicates(i => i);
    }

    public static bool IsMultiple<T>(this IEnumerable<T> source)
    {
        var enumerator = source.GetEnumerator();
        return enumerator.MoveNext() && enumerator.MoveNext();
    }
}

在Duplicates方法中使用IsMultiple()比使用Count()更快,因为它不会遍历整个集合。


2
如果您查看Grouping的参考源代码,您会发现Count()已经被预先计算,而您的解决方案可能会更慢。 - Johnbot
1
@Johnbot。你是对的,在这种情况下,它更快,实现可能永远不会改变...但这取决于IGrouping背后的实现类的实现细节。使用我的实现,你知道它永远不会迭代整个集合。 - Alex Siepman
1
因此,计数[Count()]基本上与遍历整个列表不同。Count()是预先计算的,但遍历整个列表则不是。 - Jogi
2
@RehanKhan:IsMultiple并没有执行Count(),它在2个项目后立即停止。就像Take(2).Count >= 2; - Alex Siepman
@AlexSiepman,我理解你未来实现更改的逻辑,并且我喜欢你的IsMultiple方法,它很聪明。但是为了其他访问者,请注意:Count() > 1在今天的情况下肯定比像IsMultipleSkip(1).Any()这样的检查要快。而且不要忘记,在这个实现中我们还没有处理枚举器。另一个快速选项是MoreLINQ的AtLeast方法。你可以在这里使用AtLeast(2)。类型检查和获取计数属性比运行枚举器并处理它要快。当然,所有这些都属于微优化,你不应该关心。 - nawfal
显示剩余2条评论

6

我创建了一个扩展来响应这个问题,你可以将其包含在你的项目中。我认为这是在搜索列表或Linq中重复项时返回最多的情况。

示例:

//Dummy class to compare in list
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public Person(int id, string name, string surname)
    {
        this.Id = id;
        this.Name = name;
        this.Surname = surname;
    }
}


//The extention static class
public static class Extention
{
    public static IEnumerable<T> getMoreThanOnceRepeated<T>(this IEnumerable<T> extList, Func<T, object> groupProps) where T : class
    { //Return only the second and next reptition
        return extList
            .GroupBy(groupProps)
            .SelectMany(z => z.Skip(1)); //Skip the first occur and return all the others that repeats
    }
    public static IEnumerable<T> getAllRepeated<T>(this IEnumerable<T> extList, Func<T, object> groupProps) where T : class
    {
        //Get All the lines that has repeating
        return extList
            .GroupBy(groupProps)
            .Where(z => z.Count() > 1) //Filter only the distinct one
            .SelectMany(z => z);//All in where has to be retuned
    }
}

//how to use it:
void DuplicateExample()
{
    //Populate List
    List<Person> PersonsLst = new List<Person>(){
    new Person(1,"Ricardo","Figueiredo"), //fist Duplicate to the example
    new Person(2,"Ana","Figueiredo"),
    new Person(3,"Ricardo","Figueiredo"),//second Duplicate to the example
    new Person(4,"Margarida","Figueiredo"),
    new Person(5,"Ricardo","Figueiredo")//third Duplicate to the example
    };

    Console.WriteLine("All:");
    PersonsLst.ForEach(z => Console.WriteLine("{0} -> {1} {2}", z.Id, z.Name, z.Surname));
    /* OUTPUT:
        All:
        1 -> Ricardo Figueiredo
        2 -> Ana Figueiredo
        3 -> Ricardo Figueiredo
        4 -> Margarida Figueiredo
        5 -> Ricardo Figueiredo
        */

    Console.WriteLine("All lines with repeated data");
    PersonsLst.getAllRepeated(z => new { z.Name, z.Surname })
        .ToList()
        .ForEach(z => Console.WriteLine("{0} -> {1} {2}", z.Id, z.Name, z.Surname));
    /* OUTPUT:
        All lines with repeated data
        1 -> Ricardo Figueiredo
        3 -> Ricardo Figueiredo
        5 -> Ricardo Figueiredo
        */
    Console.WriteLine("Only Repeated more than once");
    PersonsLst.getMoreThanOnceRepeated(z => new { z.Name, z.Surname })
        .ToList()
        .ForEach(z => Console.WriteLine("{0} -> {1} {2}", z.Id, z.Name, z.Surname));
    /* OUTPUT:
        Only Repeated more than once
        3 -> Ricardo Figueiredo
        5 -> Ricardo Figueiredo
        */
}

1
考虑使用Skip(1).Any()代替Count()。如果您有1000个重复项,则Skip(1).Any()将在找到第二个重复项后停止。而Count()会访问所有1000个元素。 - Harald Coppoolse
1
如果您添加此扩展方法,请考虑使用HashSet.Add而不是GroupBy,正如其他答案中建议的那样。一旦HashSet.Add找到重复项,它就会停止。您的GroupBy将继续对所有元素进行分组,即使已经找到具有多个元素的组。 - Harald Coppoolse

3

有一个答案,但我不明白为什么它不能工作。

var anyDuplicate = enumerable.GroupBy(x => x.Key).Any(g => g.Count() > 1);

在这种情况下,我的解决方案是这样的:

var duplicates = model.list
                    .GroupBy(s => s.SAME_ID)
                    .Where(g => g.Count() > 1).Count() > 0;
if(duplicates) {
    doSomething();
}

第一种语法不起作用,因为它实际上是一个布尔扩展:如果至少有一个元素满足谓词,ANY方法将返回true,否则返回false。因此,您的代码只会告诉您是否有重复项,而不会告诉您哪些是重复项。 - Silviu Preda

2

这里有一种其他的方法:

仅针对HasDuplicate

bool hasAnyDuplicate = list.Count > list.Distinct().Count;

对于重复值

List<string> duplicates = new List<string>();
duplicates.AddRange(list);
list.Distinct().ToList().ForEach(x => duplicates.Remove(x));

// for unique duplicate values:
duplicates.Distinct():

1

在MS SQL Server中检查了完整的Linq to SQL扩展的重复函数集。不使用.ToList()或IEnumerable。这些查询在SQL Server中执行而不是在内存中执行。结果仅在内存中返回。

public static class Linq2SqlExtensions {

    public class CountOfT<T> {
        public T Key { get; set; }
        public int Count { get; set; }
    }

    public static IQueryable<TKey> Duplicates<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).Select(s => s.Key);

    public static IQueryable<TSource> GetDuplicates<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).SelectMany(s => s);

    public static IQueryable<CountOfT<TKey>> DuplicatesCounts<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).Select(y => new CountOfT<TKey> { Key = y.Key, Count = y.Count() });

    public static IQueryable<Tuple<TKey, int>> DuplicatesCountsAsTuble<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).Select(s => Tuple.Create(s.Key, s.Count()));
}

1

这是更简单的方法,不需要使用分组,只需获取区域元素,然后对它们进行迭代,并检查它们在列表中的计数,如果它们计数>1,则表示它出现了多个项目,因此将其添加到Repeteditemlist中。

var mylist = new List<int>() { 1, 1, 2, 3, 3, 3, 4, 4, 4 };
            var distList=  mylist.Distinct().ToList();
            var Repeteditemlist = new List<int>();
            foreach (var item in distList)
            {
               if(mylist.Count(e => e == item) > 1)
                {
                    Repeteditemlist.Add(item);
                }
            }
            foreach (var item in Repeteditemlist)
            {
                Console.WriteLine(item);
            }

预期输出: 1 3 4

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