【问题标题】:How to remove duplicates from a List<T>?如何从 List<T> 中删除重复项?
【发布时间】:2011-04-19 13:35:59
【问题描述】:

我正在关注 stackoverflow 上的 previous post 关于从 C# 中的 List&lt;T&gt; 中删除重复项。

如果&lt;T&gt; 是一些用户定义的类型,例如:

class Contact
{
    public string firstname;
    public string lastname;
    public string phonenum;
}

建议的 (HashMap) 不会删除重复项。我想,我必须重新定义一些比较两个对象的方法,不是吗?

【问题讨论】:

    标签: c# list


    【解决方案1】:

    HashSet&lt;T&gt;确实删除重复项,因为它是一个集合...但仅当您的类型适当地定义了相等性时。

    我怀疑“重复”是指“与另一个对象具有相同字段值的对象”-您需要覆盖 Equals/GetHashCode 才能使其工作,和/或实现 IEquatable&lt;Contact&gt;... 或你可以为HashSet&lt;T&gt; 构造函数提供一个IEqualityComparer&lt;Contact&gt;

    可以只调用Distinct LINQ 扩展方法,而不是使用HashSet&lt;T&gt;。例如:

    list = list.Distinct().ToList();
    

    但同样,您需要以某种方式提供适当的平等定义。

    这是一个示例实现。注意我是如何使它不可变的(可变类型的相等是奇怪的,因为两个对象可以在一分钟相等而下一分钟不相等)和 制成 字段私有,具有公共属性。最后,我已经密封了这个类——不可变类型通常应该是密封的,它使平等更容易讨论。

    using System;
    using System.Collections.Generic; 
    
    public sealed class Contact : IEquatable<Contact>
    {
        private readonly string firstName;
        public string FirstName { get { return firstName; } }
    
        private readonly string lastName;
        public string LastName { get { return lastName; } }
    
        private readonly string phoneNumber;
        public string PhoneNumber { get { return phoneNumber; } }
    
        public Contact(string firstName, string lastName, string phoneNumber)
        {
            this.firstName = firstName;
            this.lastName = lastName;
            this.phoneNumber = phoneNumber;
        }
    
        public override bool Equals(object other)
        {
            return Equals(other as Contact);
        }
    
        public bool Equals(Contact other)
        {
            if (object.ReferenceEquals(other, null))
            {
                return false;
            }
            if (object.ReferenceEquals(other, this))
            {
                return true;
            }
            return FirstName == other.FirstName &&
                   LastName == other.LastName &&
                   PhoneNumber == other.PhoneNumber;
        }
    
        public override int GetHashCode()
        {
            // Note: *not* StringComparer; EqualityComparer<T>
            // copes with null; StringComparer doesn't.
            var comparer = EqualityComparer<string>.Default;
    
            // Unchecked to allow overflow, which is fine
            unchecked
            {
                int hash = 17;
                hash = hash * 31 + comparer.GetHashCode(FirstName);
                hash = hash * 31 + comparer.GetHashCode(LastName);
                hash = hash * 31 + comparer.GetHashCode(PhoneNumber);
                return hash;
            }
        }
    }
    

    编辑:好的,响应对GetHashCode() 实现的解释请求:

    • 我们想结合这个对象的属性的哈希码
    • 我们不会在任何地方检查是否为空,因此我们应该假设其中一些可能为空。 EqualityComparer&lt;T&gt;.Default 总是处理这个,这很好......所以我用它来获取每个字段的哈希码。
    • 将多个哈希码组合为一个的“加法和乘法”方法是 Josh Bloch 推荐的标准方法。还有很多其他通用的哈希算法,但这个算法适用于大多数应用程序。
    • 我不知道您是否在默认情况下在已检查的上下文中进行编译,因此我将计算置于未检查的上下文中。我们真的不在乎重复的乘法/加法是否会导致溢出,因为我们不是在寻找这样的“数量”......只是一个我们可以重复达到相等的数字对象。

    顺便说一下,两种处理无效的方法:

    public override int GetHashCode()
    {
        // Unchecked to allow overflow, which is fine
        unchecked
        {
            int hash = 17;
            hash = hash * 31 + (FirstName ?? "").GetHashCode();
            hash = hash * 31 + (LastName ?? "").GetHashCode();
            hash = hash * 31 + (PhoneNumber ?? "").GetHashCode();
            return hash;
        }
    }
    

    public override int GetHashCode()
    {
        // Unchecked to allow overflow, which is fine
        unchecked
        {
            int hash = 17;
            hash = hash * 31 + (FirstName == null ? 0 : FirstName.GetHashCode());
            hash = hash * 31 + (LastName == null ? 0 : LastName.GetHashCode());
            hash = hash * 31 + (PhoneNumber == null ? 0 : PhoneNumber.GetHashCode());
            return hash;
        }
    }
    

    【讨论】:

    • 正是我所写的(如果我没有太晚的话:-)
    • GetHashCode() 的实现需要附带解释性 cmets。我的大脑无法达到你的水平。
    • 能否请您使用 GetHashCode 方法,我似乎没有遵循。
    • @Sandy, @Chris:在底部添加了关于哈希码的编辑。抱歉,拖了这么久。
    【解决方案2】:
    class Contact {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public override string ToString()
        {
            return string.Format("{0}:{1}", Id, Name);
        }
    
        static private IEqualityComparer<Contact> comparer;
        static public IEqualityComparer<Contact> Comparer {
            get { return comparer ?? (comparer = new EqualityComparer()); }
        }
    
        class EqualityComparer : IEqualityComparer<Contact> {
            bool IEqualityComparer<Contact>.Equals(Contact x, Contact y)
            {
                if (x == y) 
                    return true;
    
                if (x == null || y == null)
                    return false;
    
                return x.Name == y.Name; // let's compare by Name
            }
    
            int IEqualityComparer<Contact>.GetHashCode(Contact c)
            {
                return c.Name.GetHashCode(); // let's compare by Name
            }
        }
    }
    
    class Program {
        public static void Main()
        {
            var list = new List<Contact> {
                new Contact { Id = 1, Name = "John" },
                new Contact { Id = 2, Name = "Sylvia" },
                new Contact { Id = 3, Name = "John" }
            };
    
            var distinctNames = list.Distinct(Contact.Comparer).ToList();
            foreach (var contact in distinctNames)
                Console.WriteLine(contact);
        }
    }
    

    给予

    1:John
    2:Sylvia
    

    【讨论】:

      【解决方案3】:

      对于这项任务,我不一定认为实施 IComparable 是显而易见的解决方案。您可能希望以多种不同方式对唯一性进行排序和测试。

      我倾向于实现IEqualityComparer&lt;Contact&gt;

      sealed class ContactFirstNameLastNameComparer : IEqualityComparer<Contact>
      {
        public bool Equals (Contact x, Contact y)
        {
           return x.firstname == y.firstname && x.lastname == y.lastname;
        }
      
        public int GetHashCode (Contact obj)
        {
           return obj.firstname.GetHashCode () ^ obj.lastname.GetHashCode ();
        }
      }
      

      然后使用System.Linq.Enumerable.Distinct(假设您至少使用.NET 3.5)

      var unique = contacts.Distinct (new ContactFirstNameLastNameComparer ()).ToArray ();
      

      附言。说到HashSet&lt;&gt; 请注意HashSet&lt;&gt;IEqualityComparer&lt;&gt; 作为构造函数参数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-09-08
        • 1970-01-01
        • 1970-01-01
        • 2015-05-23
        • 2012-09-28
        • 2019-10-13
        • 1970-01-01
        相关资源
        最近更新 更多