C# 从 List<List<int>> 中删除重复项

Posted

技术标签:

【中文标题】C# 从 List<List<int>> 中删除重复项【英文标题】:C# remove duplicates from List<List<int>> 【发布时间】:2012-09-28 21:13:47 【问题描述】:

例如,我无法想出最有效的算法来删除 List&lt;List&lt;int&gt;&gt; 中的重复项(我知道这看起来像 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 化并使其在我的课堂上工作。

【讨论】:

请注意,List 中的 T 必须实现 IComparable。如果 T 是自定义类型,则您必须自己执行此操作。 @MichaelSallmen 我已经展示了一个实现,它可以选择使用IEqualityComparer&lt;T&gt; 来指定如何比较T 对象。不过,好点。 (未定义适当比较接口的类型的默认相等比较器将仅检查引用相等) @Servy 是的,确实如此。不过,这只是一个示例实现。写一个好的不是微不足道的(例如看到 Coung Le 的更好的实现,仍然有问题)。也许在异或之前将每个项目的哈希乘以下一个更大的素数会更好? @TimS。我只想留下//TODO generate hash,而不是把你所拥有的;至少这样读者会知道他们需要自己找到一个好的算法,而不会认为这很好。 @Servy 我已经用一个好的实现替换了它。否则,我会同意你的看法。【参考方案4】:

构建EqualityComparer&lt;List&lt;int&gt;&gt;的自定义:

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) =&gt; (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> [重复]

从 Enumerable.Range 或 List<int> 填充 List<dynamic>

C# 中如何从List集合中提取第一个值

如何在 C# 中创建 List<int> 数组?

C# 如何把两个list的元素相加

c# List<int[]> 如何转成 string?