删除 int 数组列表中的重复项

Posted

技术标签:

【中文标题】删除 int 数组列表中的重复项【英文标题】:Delete duplicates in a List of int arrays 【发布时间】:2016-10-17 10:31:00 【问题描述】:

有一个 int 数组列表,例如:

List<int[]> intArrList = new List<int[]>();
intArrList.Add(new int[3]  0, 0, 0 );
intArrList.Add(new int[5]  20, 30, 10, 4, 6 );  //this
intArrList.Add(new int[3]  1, 2, 5 );
intArrList.Add(new int[5]  20, 30, 10, 4, 6 );  //this
intArrList.Add(new int[3]  12, 22, 54 );
intArrList.Add(new int[5]  1, 2, 6, 7, 8 );
intArrList.Add(new int[4]  0, 0, 0, 0 );

您将如何删除重复项(重复项是指列表的元素具有相同的长度和相同的数字)。

在示例中,我将删除元素 20, 30, 10, 4, 6 ,因为它被发现两次

我正在考虑按元素大小对列表进行排序,然后将每个元素与其余元素循环,但我不知道该怎么做。

另一个问题是,如果使用像哈希这样的其他结构会更好......如果是这样,如何使用它?

【问题讨论】:

它们的顺序是否相同以及数字是否相同但一个列表中有一个重复的数字是否重要? 小心!对这个问题的稳健和高效的回答并不容易实现,而对这个问题的似是而非的和/或缓慢的解决方案是微不足道的 @J.P 所以这个问题没有答案让你满意吗?如果是这样,为什么?太慢了,太丑了? @J.P HashSet ,注意名称中的“set”。效率会高得多,但是按原样解决问题是不可行的。存储数据是不可能的。在一个集合中, order(ing) 没有意义并且没有重复。 Hashet 还有一个名为 SetEquals 的方法,它比现有解决方案中的比较更有效。然而,重复和排序是问题的核心。如果没有这些要求,它是微不足道的。自定义类或 Dictionary 可能是可行的。我明天试试。现在我睡觉了:) @Evk 抱歉,出于这个原因,我想为这个问题开始赏金这个问题没有得到足够的关注。 但不幸的是,我忘了更改赏金原因选项,所以它采用默认选项。实际上,所有的答案都相当好和正确。我不知道是否有办法编辑赏金原因,以免产生误导。 【参考方案1】:

使用GroupBy:

var result = intArrList.GroupBy(c => String.Join(",", c))
                       .Select(c => c.First().ToList()).ToList();

结果:

0, 0, 0

20、30、10、4、6

1、2、5

12、22、54

1、2、6、7、8

0, 0, 0, 0

编辑:如果您想考虑1,2,3,4 等于2,3,4,1,您需要像这样使用OrderBy

var result = intArrList.GroupBy(p => string.Join(", ", p.OrderBy(c => c)))
                       .Select(c => c.First().ToList()).ToList(); 

EDIT2:为帮助理解 LINQ GroupBy 解决方案的工作原理,请考虑以下方法:

public List<int[]> FindDistinctWithoutLinq(List<int[]> lst)

    var dic = new Dictionary<string, int[]>();
    foreach (var item in lst)
    
        string key = string.Join(",", item.OrderBy(c=>c));

        if (!dic.ContainsKey(key))
        
            dic.Add(key, item);
        
    

    return dic.Values.ToList();

【讨论】:

您也可以实现一个EqualityComparer 类并在LINQ 的Distinct 方法中使用它。但我认为为此目的使用GroupBy 会更简单 GropuBy 认为1,2,3,4 等于2,3,4,1 吗? 您可以在分组之前在 c 上使用 order by,这样它就会相等 我有这样的想法: var result = intArrList.GroupBy(p => string.Join(", ", p.OrderBy(c => c))).Select(c = > c.First().ToList()).ToList(); @cMinor...我提供了一种方法来帮助您了解基于 LINQ 的解决方案的工作原理。再次查看更新的答案。【参考方案2】:

您可以定义自己的IEqualityComparer 实现并将其与IEnumerable.Distinct 一起使用:

class MyComparer : IEqualityComparer<int[]> 

    public int GetHashCode(int[] instance)  return 0;  // TODO: better HashCode for arrays
    public bool Equals(int[] instance, int[] other)
    
        if (other == null || instance == null || instance.Length != other.Length) return false;

        return instance.SequenceEqual(other);
    

现在写这个来只为你的列表获取不同的值:

var result = intArrList.Distinct(new MyComparer());

但是,如果您还想要不同的排列,您应该以这种方式实现您的比较器:

public bool Equals(int[] instance, int[] other)

    if (ReferenceEquals(instance, other)) return true; // this will return true when both arrays are NULL
    if (other == null || instance == null) return false;
    return instance.All(x => other.Contains(x)) && other.All(x => instance.Contains(x));

编辑:为了更好的GetashCode-实现,您可以查看this post,正如@Mick 的回答中所建议的那样。

【讨论】:

此解决方案是否会按照 OP 的要求考虑 1,2,3,4 等于 2,3,4,1 当然,正如您在第二个Equals-implementation 中看到的那样。还是我错过了什么?【参考方案3】:

来自here 和here 的提升代码。更通用的 GetHashCode 实现会使它更通用,但是我相信下面的实现是最健壮的

class Program

    static void Main(string[] args)
    
        List<int[]> intArrList = new List<int[]>();
        intArrList.Add(new int[3]  0, 0, 0 );
        intArrList.Add(new int[5]  20, 30, 10, 4, 6 );  //this
        intArrList.Add(new int[3]  1, 2, 5 );
        intArrList.Add(new int[5]  20, 30, 10, 4, 6 );  //this
        intArrList.Add(new int[3]  12, 22, 54 );
        intArrList.Add(new int[5]  1, 2, 6, 7, 8 );
        intArrList.Add(new int[4]  0, 0, 0, 0 );

        var test = intArrList.Distinct(new IntArrayEqualityComparer());
        Console.WriteLine(test.Count());
        Console.WriteLine(intArrList.Count());
    

    public class IntArrayEqualityComparer : IEqualityComparer<int[]>
    
        public bool Equals(int[] x, int[] y)
        
            return ArraysEqual(x, y);
        

        public int GetHashCode(int[] obj)
        
            int hc = obj.Length;
            for (int i = 0; i < obj.Length; ++i)
            
                hc = unchecked(hc * 17 + obj[i]);
            
            return hc;
        

        static bool ArraysEqual<T>(T[] a1, T[] a2)
        
            if (ReferenceEquals(a1, a2))
                return true;

            if (a1 == null || a2 == null)
                return false;

            if (a1.Length != a2.Length)
                return false;

            EqualityComparer<T> comparer = EqualityComparer<T>.Default;
            for (int i = 0; i < a1.Length; i++)
            
                if (!comparer.Equals(a1[i], a2[i])) return false;
            
            return true;
        
    

编辑: IEqualityComparer 的通用实现,适用于任何类型的数组:-

public class ArrayEqualityComparer<T> : IEqualityComparer<T[]>

    public bool Equals(T[] x, T[] y)
    
        if (ReferenceEquals(x, y))
            return true;

        if (x == null || y == null)
            return false;

        if (x.Length != y.Length)
            return false;

        EqualityComparer<T> comparer = EqualityComparer<T>.Default;
        for (int i = 0; i < x.Length; i++)
        
            if (!comparer.Equals(x[i], y[i])) return false;
        
        return true;
    

    public int GetHashCode(T[] obj)
    
        int hc = obj.Length;
        for (int i = 0; i < obj.Length; ++i)
        
            hc = unchecked(hc * 17 + obj[i].GetHashCode());
        
        return hc;
    

Edit2:如果数组中整数的排序无关紧要,我会

var test = intArrList.Select(a => a.OrderBy(e => e).ToArray()).Distinct(comparer).ToList();

【讨论】:

很好HashCode-implementation。 我不相信它,因为它来自链接之一。良好的哈希码实现是一件非常棘手的事情。一个平均的 Hashcode 实现将在 99.99999% 的时间内工作,并且在 0.00001% 的时间里相当直接地咬你。 这就是为什么我把我的负担集中在有用的东西上:)。不过,好的答案,赞成。 x!= null 和 y == null 返回 false,很公平。无论如何,我认为这个解决方案是最有效的解决方案,与接受的答案相比,它对于更大的列表(就内存而言)也可以更好地扩展。试图想出我自己的答案,但遗憾的是,理论并不像重复。但是有一个问题:您认为 GroupBy(l => l.Count) 然后 SelectMany.Distinct 并且只对需要比较的列表进行排序,即 group.Count>1 会更好吗?我的直觉会说这会导致比较少,但也许这是 CLR 已经优化的东西? 我的意思是在组内使用 Distinct(相同的计数),然后使用 SelectMany。当然,我很可能完全错了。我知道 GroupBy 也很常见,在我看来,降低对所有列表进行排序并在整个列表上调用 distinct 的成本似乎是值得的。【参考方案4】:
List<int[]> CopyString1 = new List<int[]>();
CopyString1.AddRange(intArrList);
List<int[]> CopyString2 = new List<int[]>();
CopyString2.AddRange(intArrList);
for (int i = 0; i < CopyString2.Count(); i++)

    for (int j = i; j < CopyString1.Count(); j++)
    
        if (i != j && CopyString2[i].Count() == CopyString1[j].Count())
        
            var cnt = 0;
            for (int k = 0; k < CopyString2[i].Count(); k++)
            
                if (CopyString2[i][k] == CopyString1[j][k])
                    cnt++;
                else
                    break;
            
            if (cnt == CopyString2[i].Count())
                intArrList.RemoveAt(i);
        
    

【讨论】:

你测试过这个实现吗?第一次删除后RemoveAt(i) 肯定不正确吗?当您在列表中向上迭代 i 时,删除第一个条目后,CopyString1intArrList 中的元素不再对齐。【参考方案5】:

使用 BenchmarkDotNet 比较 @S.Akbari 和 @Mick 的解决方案

编辑:

SAkbari_FindDistinctWithoutLinq 对 ContainsKey 有冗余调用,因此我添加了改进和更快的版本:SAkbari_FindDistinctWithoutLinq2

方法 |平均值 |错误 |标准差 | --------------------------------- |---------:|------ -----:|----------:| SAkbari_FindDistinctWithoutLinq | 4.021 我们 | 0.0723 我们 | 0.0676 我们 | SAkbari_FindDistinctWithoutLinq2 | 3.930 我们 | 0.0529 我们 | 0.0495 我们 | SAkbari_FindDistinctLinq | 5.597 我们 | 0.0264 我们 | 0.0234 我们 | Mick_UsingGetHashCode | 6.339 我们 | 0.0265 我们 | 0.0248 我们 | BenchmarkDotNet=v0.10.13,操作系统=Windows 10 Redstone 3 [1709,秋季创作者更新] (10.0.16299.248) Intel Core i7-7700 CPU 3.60GHz (Kaby Lake),1个CPU,8个逻辑核心和4个物理核心 频率=3515625 Hz,分辨率=284.4444 ns,定时器=TSC .NET Core SDK=2.1.100 [主机]:.NET Core 2.0.5(CoreCLR 4.6.26020.03,CoreFX 4.6.26018.01),64bit RyuJIT DefaultJob:.NET Core 2.0.5(CoreCLR 4.6.26020.03,CoreFX 4.6.26018.01),64 位 RyuJIT

基准测试:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1

    public class Program
    
        List<int[]> intArrList = new List<int[]>
        
            new int[]  0, 0, 0 ,
            new int[]  20, 30, 10, 4, 6 ,  //this
            new int[]  1, 2, 5 ,
            new int[]  20, 30, 10, 4, 6 ,  //this
            new int[]  12, 22, 54 ,
            new int[]  1, 2, 6, 7, 8 ,
            new int[]  0, 0, 0, 0 
        ;

        [Benchmark]
        public List<int[]> SAkbari_FindDistinctWithoutLinq() => FindDistinctWithoutLinq(intArrList);

        [Benchmark]
        public List<int[]> SAkbari_FindDistinctWithoutLinq2() => FindDistinctWithoutLinq2(intArrList);

        [Benchmark]
        public List<int[]> SAkbari_FindDistinctLinq() => FindDistinctLinq(intArrList);

        [Benchmark]
        public List<int[]> Mick_UsingGetHashCode() => FindDistinctLinq(intArrList);

        static void Main(string[] args)
        
            var summary = BenchmarkRunner.Run<Program>();
        

        public static List<int[]> FindDistinctWithoutLinq(List<int[]> lst)
        
            var dic = new Dictionary<string, int[]>();
            foreach (var item in lst)
            
                string key = string.Join(",", item.OrderBy(c => c));

                if (!dic.ContainsKey(key))
                
                    dic.Add(key, item);
                
            

            return dic.Values.ToList();
        

        public static List<int[]> FindDistinctWithoutLinq2(List<int[]> lst)
        
            var dic = new Dictionary<string, int[]>();

            foreach (var item in lst)
                dic.TryAdd(string.Join(",", item.OrderBy(c => c)), item);

            return dic.Values.ToList();
        

        public static List<int[]> FindDistinctLinq(List<int[]> lst)
        
            return lst.GroupBy(p => string.Join(", ", p.OrderBy(c => c)))
                       .Select(c => c.First().ToArray()).ToList();
        

        public static List<int[]> UsingGetHashCode(List<int[]> lst)
        
            return lst.Select(a => a.OrderBy(e => e).ToArray()).Distinct(new IntArrayEqualityComparer()).ToList();
        
    

    public class IntArrayEqualityComparer : IEqualityComparer<int[]>
    
        public bool Equals(int[] x, int[] y)
        
            return ArraysEqual(x, y);
        

        public int GetHashCode(int[] obj)
        
            int hc = obj.Length;
            for (int i = 0; i < obj.Length; ++i)
            
                hc = unchecked(hc * 17 + obj[i]);
            
            return hc;
        

        static bool ArraysEqual<T>(T[] a1, T[] a2)
        
            if (ReferenceEquals(a1, a2))
                return true;

            if (a1 == null || a2 == null)
                return false;

            if (a1.Length != a2.Length)
                return false;

            EqualityComparer<T> comparer = EqualityComparer<T>.Default;
            for (int i = 0; i < a1.Length; i++)
            
                if (!comparer.Equals(a1[i], a2[i])) return false;
            
            return true;
        
    

【讨论】:

【参考方案6】:

输入列表;

List<List<int>> initList = new List<List<int>>();
initList.Add(new List<int> 0, 0, 0 );
initList.Add(new List<int> 20, 30, 10, 4, 6 );  //this
initList.Add(new List<int>  1, 2, 5 );
initList.Add(new List<int>  20, 30, 10, 4, 6 );  //this
initList.Add(new List<int>  12, 22, 54 );
initList.Add(new List<int>  1, 2, 6, 7, 8 );
initList.Add(new List<int>  0, 0, 0, 0 );

您可以创建一个结果列表,在添加元素之前您可以检查它是否已经添加。我只是比较了列表计数并使用p.Except(item).Any() 调用来检查列表是否包含该元素。

List<List<int>> returnList = new List<List<int>>();

foreach (var item in initList)

    if (returnList.Where(p => !p.Except(item).Any() && !item.Except(p).Any()
                             && p.Count() == item.Count() ).Count() == 0)
    returnList.Add(item);

【讨论】:

【参考方案7】:

您可以使用 HashSet。 HashSet 是一个用于保证唯一性的集合,您可以比较集合、相交、联合上的项目。等等

优点:没有重复,易于操作数据组,更高效 缺点:您无法获取集合中的特定项目,例如:list[0] 不适用于 HashSet。您只能枚举项目。例如前锋

这是一个例子:

using System;
using System.Collections.Generic;

namespace ConsoleApp2

    class Program
    
        static void Main(string[] args)
        
            HashSet<HashSet<int>> intArrList = new HashSet<HashSet<int>>(new HashSetIntComparer());
            intArrList.Add(new HashSet<int>(3)  0, 0, 0 );
            intArrList.Add(new HashSet<int>(5)  20, 30, 10, 4, 6 );  //this
            intArrList.Add(new HashSet<int>(3)  1, 2, 5 );
            intArrList.Add(new HashSet<int>(5)  20, 30, 10, 4, 6 );  //this
            intArrList.Add(new HashSet<int>(3)  12, 22, 54 );
            intArrList.Add(new HashSet<int>(5)  1, 2, 6, 7, 8 );
            intArrList.Add(new HashSet<int>(4)  0, 0, 0, 0 );

            // Checking the output
            foreach (var item in intArrList)
            
                foreach (var subHasSet in item)
                
                    Console.Write("0 ", subHasSet);
                

                Console.WriteLine();
                        

            Console.Read();
        

        private class HashSetIntComparer : IEqualityComparer<HashSet<int>>
        
            public bool Equals(HashSet<int> x, HashSet<int> y)
            
                // SetEquals does't set anything. It's a method for compare the contents of the HashSet. 
                // Such a poor name from .Net
                return x.SetEquals(y);
            

            public int GetHashCode(HashSet<int> obj)
            
                //TODO: implemente a better HashCode
                return base.GetHashCode();
            
        
    



Output:
0
20 30 10 4 6
1 2 5
12 22 54
1 2 6 7 8

注意:由于 0 重复多次,HashSet 只考虑 0 一次。如果你需要区分 0 0 0 0 和 0 0 0 那么你可以 替换 HashSet&lt;HashSet&lt;int&gt;&gt; for HashSet&lt;List&lt;int&gt;&gt; 并实施 改为列表的比较器。

您可以使用此链接了解如何比较列表: https://social.msdn.microsoft.com/Forums/en-US/2ff3016c-bd61-4fec-8f8c-7b6c070123fa/c-compare-two-lists-of-objects?forum=csharplanguage

如果您想了解有关集合和数据类型的更多信息,本课程是学习它的理想场所: https://app.pluralsight.com/player?course=csharp-collections&author=simon-robinson&name=csharp-collections-fundamentals-m9-sets&clip=1&mode=live

【讨论】:

【参考方案8】:

使用 MoreLINQ 可以非常简单地使用 DistinctBy。

var result = intArrList.DistinctBy(x => string.Join(",", x));

如果您希望区分不考虑顺序,则类似于 GroupBy 答案。

var result = intArrList.DistinctBy(x => string.Join(",", x.OrderBy(y => y)));

编辑:这就是它的实现方式

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
        
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));

            return _(); IEnumerable<TSource> _()
            
                var knownKeys = new HashSet<TKey>(comparer);
                foreach (var element in source)
                
                    if (knownKeys.Add(keySelector(element)))
                        yield return element;
                
            
        

所以如果你不需要 MoreLINQ 来做其他事情,你可以使用这样的方法:

private static IEnumerable<int[]> GetUniqueArrays(IEnumerable<int[]> source)
    
        var knownKeys = new HashSet<string>();
        foreach (var element in source)
        
            if (knownKeys.Add(string.Join(",", element)))
                yield return element;
        
    

【讨论】:

以上是关于删除 int 数组列表中的重复项的主要内容,如果未能解决你的问题,请参考以下文章

删除列表中的重复对象 (C#)

使用循环从字符数组列表中删除重复项? [复制]

从2D列表中删除重复项(浮点)

从 Ocaml 中的列表列表中删除重复项?

使用 Python 删除对象列表中的重复项

删除列表中的重复项