在C#中检查对象列表中的重复项

41

我正在寻找一种快速检查对象列表中重复项的方法。

我考虑直接循环遍历列表并进行手动比较,但是我认为Linq可能会提供更优雅的解决方案...

假设我有一个对象...

public class dupeCheckee
{
     public string checkThis { get; set; }
     public string checkThat { get; set; }

     dupeCheckee(string val, string val2)
     {
         checkThis = val;
         checkThat = val2;
     }
}

我有一个对象列表

List<dupeCheckee> dupList = new List<dupeCheckee>();
dupList.Add(new dupeCheckee("test1", "value1"));
dupList.Add(new dupeCheckee("test2", "value1"));
dupList.Add(new dupeCheckee("test3", "value1"));
dupList.Add(new dupeCheckee("test1", "value1"));//dupe
dupList.Add(new dupeCheckee("test2", "value1"));//dupe... 
dupList.Add(new dupeCheckee("test4", "value1"));
dupList.Add(new dupeCheckee("test5", "value1"));
dupList.Add(new dupeCheckee("test1", "value2"));//not dupe

我需要在那个列表中找到重复项。找到后,我需要做一些额外的逻辑处理,不一定是删除它们。

当我使用linq时,我的GroupBy出现异常...

'System.Collections.Generic.List<dupeCheckee>' does not contain a definition for 'GroupBy' and no extension method 'GroupBy' accepting a first argument of type 'System.Collections.Generic.List<dupeCheckee>' could be found (are you missing a using directive or an assembly reference?)

这提示我缺少一个库,但我很难确定是哪一个。

一旦我解决了这个问题,如何检查这两个条件是否都出现了超过一次呢?即检查checkThis和checkThat是否都出现了多次。

更新:我想到的解决方案

这是我在快速研究后想出的linq查询...

test.Count != test.Select(c => new { c.checkThat, c.checkThis }).Distinct().Count()

我不确定这是否绝对比这个答案更好...

var duplicates = test.GroupBy(x => new {x.checkThis, x.checkThat})
                   .Where(x => x.Skip(1).Any());

我知道可以将第一个语句放入if else子句中。 我也进行了快速测试。 当我期望得到0时,重复项列表返回给我1,但它确实正确地指出了我在其中一个集合中有重复项...

另一种方法正如我所预期的那样运行。 这是我用来测试的数据集....

Dupes:

List<DupeCheckee> test = new List<DupeCheckee>{ 
     new DupeCheckee("test0", "test1"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test1", "test2"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test2", "test3"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test3", "test3"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test0", "test5"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test1", "test6"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test2", "test7"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test3", "test8"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test0", "test5"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test1", "test1"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test2", "test2"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test3", "test3"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test4", "test4"),//{ checkThis = "test", checkThat = "test1"}

};

无重复项...

     List<DupeCheckee> test2 = new List<DupeCheckee>{ 
     new DupeCheckee("test0", "test1"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test1", "test2"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test2", "test3"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test3", "test3"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test4", "test5"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test5", "test6"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test6", "test7"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test7", "test8"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test8", "test5"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test9", "test1"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test2", "test2"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test3", "test3"),//{ checkThis = "test", checkThat = "test1"}
     new DupeCheckee("test4", "test4"),//{ checkThis = "test", checkThat = "test1"}

};

1
在你的.cs文件顶部加入 using System.Linq;,使得 GroupBy 能够工作。 - Daniel Hilgarth
是的。我刚发现我错过了它。谢谢。 - SoftwareSavant
2
Erm No dupes 有一个重复测试3,测试3。 - Bob Vale
8个回答

66
你需要引用System.Linq (例如 using System.Linq)然后你可以这样做:
var dupes = dupList.GroupBy(x => new {x.checkThis, x.checkThat})
                   .Where(x => x.Skip(1).Any());

这将给您包含所有重复项的组。

重复项的测试将如下进行。

var hasDupes = dupList.GroupBy(x => new {x.checkThis, x.checkThat})
                   .Where(x => x.Skip(1).Any()).Any();

或者甚至调用ToList()ToArray()强制计算结果,然后您可以检查重复项并检查它们。

例如...

var dupes = dupList.GroupBy(x => new {x.checkThis, x.checkThat})
                   .Where(x => x.Skip(1).Any()).ToArray();
if (dupes.Any()) {
  foreach (var dupeList in dupes) {
    Console.WriteLine(string.Format("checkThis={0},checkThat={1} has {2} duplicates",
                      dupList.Key.checkThis, 
                      dupList.Key.checkThat,
                      dupList.Count() - 1));
  }

}

或者

var dupes = dupList.Select((x, i) => new { index = i, value = x})
                   .GroupBy(x => new {x.value.checkThis, x.value.checkThat})
                   .Where(x => x.Skip(1).Any());

此方法将返回一个包含每个分组的项目和原始索引的属性 index 和项目的属性 value 的数组


我真的很想看看这个项目是否有任何重复项。如果有几个“List<dupeCheckee>”中有所有的重复项,那就太好了... 如果用户以后想要删除它们,那将是很好的。但我真的只是想检查列表是否有重复项。 - SoftwareSavant
@DmainEvent 这是它的作用吗?如果你想检查是否有任何重复项,只需检查 dupes.Any() 如果为真,则存在重复项。 - Bob Vale
你能否看一下我的解决方案,看看是否有任何不足之处。我尝试了你的和我的,我的似乎没问题...不确定你的。 - SoftwareSavant
@DemainEvent 在你的原始帖子中,你指定了提取重复项的要求,而你的解决方案并没有做到。 - Bob Vale
在第二个代码片段中,你可以将 .Where(x => x.Skip(1).Any()).Any() 重写为 .Any(x => x.Skip(1).Any()) - Rudey
1
@RuudLenders 是的,你可以这样做,不过我想展示代码的进展,只是在之前的结果后面添加 any() - Bob Vale

18

之前有大量的解决方案,但我认为下一个解决方案将更加透明易懂,胜过上述所有方案:

var hasDuplicatedEntries = ListWithPossibleDuplicates
                                   .GroupBy(YourGroupingExpression)
                                   .Any(e => e.Count() > 1);
if(hasDuplicatedEntries)
{
   // Do what ever you want in case when list contains duplicates 
}

1
仅在需要实际元素数量时使用Count。它会遍历整个枚举。 - Bolpat
为了更优化的代码,请将 e => e.Count() > 1 替换为 e => e.Skip(1).Any() - Martin Ferenec

4

我喜欢使用这个工具来检查是否存在重复内容。比如说,如果你有一个字符串并想知道是否有任何重复字母,我就会用这个工具。

string text = "this is some text";

var hasDupes = text.GroupBy(x => x).Any(grp => grp.Count() > 1);

如果您想知道有多少个重复项,无论这些重复项是什么,请使用以下方法。
var totalDupeItems = text.GroupBy(x => x).Count(grp =>  grp.Count() > 1);

例如,"this is some text"中包含以下字母...
t的总数:3
i的总数:2
s的总数:3
e的总数:2
因此,变量totalDupeItems将等于4。有4种不同的重复项。
如果您想获取无论重复项是什么的总重复项数量,则使用以下内容。
var totalDupes = letters.GroupBy(x => x).Where(grp => grp.Count() > 1).Sum(grp => grp.Count());

因此,变量totalDupes将为10。这是每个重复类型的重复项总数之和。

只有在需要实际元素数量时才使用Count。它会遍历整个枚举。 - Bolpat

1
我想这就是您所寻找的内容:


List<dupeChecke> duplicates = dupeList.GroupBy(x => x)
                                   .SelectMany(g => g.Skip(1));

1
只有当对dupeCheckee进行等于检查时,checkThischeckThat相等时,两个实例才被视为相等,才能起作用。 - Bob Vale
@BobVale:没有注意到他想要进一步细分!你的评论已经点赞了。 - Captain Skyhawk

1

对于内存中的对象,我总是使用Distinct LINQ方法,并将比较器添加到解决方案中。

public class dupeCheckee
{
     public string checkThis { get; set; }
     public string checkThat { get; set; }

     dupeCheckee(string val, string val2)
     {
         checkThis = val;
         checkThat = val2;
     }

     public class Comparer : IEqualityComparer<dupeCheckee>
     {
         public bool Equals(dupeCheckee x, dupeCheckee y)
         {
             if (x == null || y == null)
                 return false;

             return x.CheckThis == y.CheckThis && x.CheckThat == y.CheckThat;
         }

         public int GetHashCode(dupeCheckee obj)
         {
             if (obj == null)
                 return 0;

             return (obj.CheckThis == null ? 0 : obj.CheckThis.GetHashCode()) ^
                 (obj.CheckThat == null ? 0 : obj.CheckThat.GetHashCode());
         }
     }
}

现在我们可以调用。
List<dupeCheckee> dupList = new List<dupeCheckee>();
dupList.Add(new dupeCheckee("test1", "value1"));
dupList.Add(new dupeCheckee("test2", "value1"));
dupList.Add(new dupeCheckee("test3", "value1"));
dupList.Add(new dupeCheckee("test1", "value1"));//dupe
dupList.Add(new dupeCheckee("test2", "value1"));//dupe... 
dupList.Add(new dupeCheckee("test4", "value1"));
dupList.Add(new dupeCheckee("test5", "value1"));
dupList.Add(new dupeCheckee("test1", "value2"));//not dupe

<b>var distinct = dupList.Distinct(dupeCheckee.Comparer);</b>

这是获取一个不同的列表,但我想弄清楚我的列表中是否有重复项。 - SoftwareSavant

0
如果出现重复项,会抛出异常。Dictionary 会自行检查键。这是最简单的方法。
try
{
  dupList.ToDictionary(a=>new {a.checkThis,a.checkThat});
}
catch{
 //message: list items is not uniqe
}

0

我为特定类型引入了扩展:

public static class CollectionExtensions
{
    public static bool HasDuplicatesByKey<TSource, TKey>(this IEnumerable<TSource> source
                                                       , Func<TSource, TKey> keySelector)
    {
        return source.GroupBy(keySelector).Any(group => group.Skip(1).Any());
    }
}

,在代码中的使用示例:

if (items.HasDuplicatesByKey(item => item.Id))
{
    throw new InvalidOperationException($@"Set {nameof(items)} has duplicates.");
}

0

使用LINQ进行选择唯一的操作,例如如何使用LINQ进行SELECT UNIQUE操作?

然后将唯一结果的计数与非唯一结果进行比较。这将给出一个布尔值,表示列表是否有重复项。

此外,您可以尝试使用字典,它将保证键是唯一的。


如果他想对重复项进行某些操作,则“GroupBy” 是更好的方法。 - Daniel Hilgarth
@Daniel 把它作为答案发布,这样我就可以点赞了,用户也可以将其标记为答案! - MatthewMartin

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