C# 从 List<List<int>> 中删除重复项
Posted
技术标签:
【中文标题】C# 从 List<List<int>> 中删除重复项【英文标题】:C# remove duplicates from List<List<int>> 【发布时间】:2012-09-28 21:13:47 【问题描述】:例如,我无法想出最有效的算法来删除 List<List<int>>
中的重复项(我知道这看起来像 int[]
的列表,但只是出于视觉目的这样做:
my_list[0]= 1, 2, 3;
my_list[1]= 1, 2, 3;
my_list[2]= 9, 10, 11;
my_list[3]= 1, 2, 3;
所以输出就是
new_list[0]= 1, 2, 3;
new_list[1]= 9, 10, 11;
如果您有任何想法,请告诉我。我真的很感激。
【问题讨论】:
1, 2, 3
是否等于 3, 2, 1
?
好吧,我知道我可以对实例中的每个元素进行排序,并且这两个元素最终会相同,因此出于此处的目的,我会说不。
我会看看下面使用 Linq 的答案,因为这大大简化了您的代码(与使用 EqualityComparers 的代码相比)。
【参考方案1】:
我想比较@Leniel Macaferi 和@L.B 答案的性能,因为我不确定哪个性能更好,或者差异是否很大。事实证明,差异非常显着:
Method 1: 00:00:00.0976649 @Leniel Macaferi
Method 2: 00:00:32.0961650 @L.B
这是我用来对它们进行基准测试的代码:
public static void Main(string[] args)
var list = new List<List<int>> new List<int> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3,, new List<int> 1, 2, 31, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 6, new List<int> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 9, 10, 11, 1, new List<int> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 9, new List<int> 1, 2, 31, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 6, 7, new List<int> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 9, 10, 11, new List<int> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3,, new List<int> 1, 2, 31, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 6, new List<int> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 9, 10, 11;
var sw1 = new Stopwatch();
sw1.Start();
for (var i = 0; i < 1_000_000; i++)
var distinct = list.Select(x => new HashSet<int>(x)).Distinct(HashSet<int>.CreateSetComparer());
sw1.Stop();
Console.WriteLine($"Method 1: sw1.Elapsed");
var sw2 = new Stopwatch();
sw2.Start();
for (var i = 0; i < 1_000_000; i++)
var distinct = list.GroupBy(a => string.Join(",", a)).Select(a => a.First()).ToList();
sw2.Stop();
Console.WriteLine($"Method 2: sw2.Elapsed");
Console.ReadKey();
【讨论】:
【参考方案2】:对于少量数据,比较器可能很有用,但如果您有 1000 个或更多列表>然后尝试比较它们可能会开始花费很长时间。
我建议您改用您的数据来构建不同的树。树的构建会更快,完成后您可以随时将数据恢复到旧数据结构中。
【讨论】:
【参考方案3】:您可以使用带有比较器的 LINQ Distinct
重载。比较器应该查看列表是否相等。请注意,列表的默认 equals 操作不会执行您真正要查找的操作,因此比较器需要为您循环遍历每个操作。以下是此类比较器的示例:
public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>>
IEqualityComparer<T> itemComparer;
public SequenceComparer()
this.itemComparer = EqualityComparer<T>.Default;
public SequenceComparer(IEqualityComparer<T> itemComparer)
this.itemComparer = itemComparer;
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
if (object.Equals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y, itemComparer);
public int GetHashCode(IEnumerable<T> obj)
if (obj == null)
return -1;
int i = 0;
return obj.Aggregate(0, (x, y) => x ^ new Index = i++, ItemHash = itemComparer.GetHashCode(y) .GetHashCode());
更新:我从 Cuong Le 的回答中得到了使用匿名类型来制作更好哈希的想法,我对其进行了 LINQ 化并使其在我的课堂上工作。
【讨论】:
请注意,ListIEqualityComparer<T>
来指定如何比较T
对象。不过,好点。 (未定义适当比较接口的类型的默认相等比较器将仅检查引用相等)
@Servy 是的,确实如此。不过,这只是一个示例实现。写一个好的不是微不足道的(例如看到 Coung Le 的更好的实现,仍然有问题)。也许在异或之前将每个项目的哈希乘以下一个更大的素数会更好?
@TimS。我只想留下//TODO generate hash
,而不是把你所拥有的;至少这样读者会知道他们需要自己找到一个好的算法,而不会认为这很好。
@Servy 我已经用一个好的实现替换了它。否则,我会同意你的看法。【参考方案4】:
构建EqualityComparer<List<int>>
的自定义:
public class CusComparer : IEqualityComparer<List<int>>
public bool Equals(List<int> x, List<int> y)
return x.SequenceEqual(y);
public int GetHashCode(List<int> obj)
int hashCode = 0;
for (var index = 0; index < obj.Count; index++)
hashCode ^= new Index = index, Item = obj[index].GetHashCode();
return hashCode;
然后你可以使用Distinct 和自定义比较器方法得到结果:
var result = my_list.Distinct(new CusComparer());
编辑:
将索引包含在方法GetHashCode
中,以确保不同的顺序不相等
【讨论】:
该哈希码会导致很多冲突 - 例如a,a
将与任何 a
发生碰撞,a,b
将与 b,a
发生任何排列碰撞...(尽管您可能想要排列发生碰撞,在这种情况下,很好的答案!)
@Rawling:你是对的,等待蒂姆评论的答案,我正在尝试修复
这是一个我觉得更好的哈希码生成器:return obj.Take(5).Aggregate(1, (current, item) => (current * 37) + item.GetHashCode());
首先,我不会迭代整个序列。哈希只有在快速生成时才有效;迭代整个列表违背了这个目的。前 5 个左右(根据需要编辑该数字)应该是好的。如果前几个相同,则列表可能不同。接下来,一个很好的通用算法形式,将 N 个不同的哈希组合成一个,就是遍历每个,将当前的一个素数相乘,然后加上下一个哈希。【参考方案5】:
var finalList = lists.GroupBy(x => String.Join(",", x))
.Select(x => x.First().ToList())
.ToList();
【讨论】:
【参考方案6】:这个简单的程序做你想做的事:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication6
class Program
static void Main(string[] args)
List<List<int>> lists = new List<List<int>>();
lists.Add(new List<int> 1, 2, 3 );
lists.Add(new List<int> 1, 2, 3 );
lists.Add(new List<int> 9, 10, 11 );
lists.Add(new List<int> 1, 2, 3 );
var distinct = lists.Select(x => new HashSet<int>(x))
.Distinct(HashSet<int>.CreateSetComparer());
foreach (var list in distinct)
foreach (var v in list)
Console.Write(v + " ");
Console.WriteLine();
【讨论】:
这确实是最好的答案,因为它充分利用了Linq来简化问题。 非常快速的解决方案。这是正确的答案。谢谢! +1以上是关于C# 从 List<List<int>> 中删除重复项的主要内容,如果未能解决你的问题,请参考以下文章
在 C# 中将 List<IEnumerable<int>> 转换为 List<int> [重复]