繁体   English   中英

HashSet如何比较元素的相等性?

[英]How does HashSet compare elements for equality?

我有一个IComparable类:

public class a : IComparable
{
    public int Id { get; set; }
    public string Name { get; set; }

    public a(int id)
    {
        this.Id = id;
    }

    public int CompareTo(object obj)
    {
        return this.Id.CompareTo(((a)obj).Id);
    }
}

当我将这个类的对象列表添加到哈希集时:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(a1);

一切都很好, ha.count2 ,但是:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(new a(1));

现在ha.count3

  1. 为什么不HashSet尊重aCompareTo方法。
  2. HashSet是拥有唯一对象列表的最佳方式吗?

它使用IEqualityComparer<T>EqualityComparer<T>.Default除非您在构造时指定不同的一个)。

当您向集合添加元素时,它将使用IEqualityComparer<T>.GetHashCode找到哈希码,并存储哈希码和元素(当然,在检查元素是否已经在集合中之后)。

要查找元素,它将首先使用IEqualityComparer<T>.GetHashCode来查找哈希码,然后对于具有相同哈希码的所有元素,它将使用IEqualityComparer<T>.Equals来比较实际的相等性。

这意味着您有两种选择:

  • 将自定义IEqualityComparer<T>传递给构造函数。 如果您无法修改T本身,或者您需要非默认的相等关系(例如“具有负用户ID的所有用户被视为相等”),则这是最佳选项。 这几乎从未在类型本身上实现(即Foo不实现IEqualityComparer<Foo> ),而是在单独的类型中仅用于比较。
  • 通过重写GetHashCodeEquals(object)在类型本身中实现相等性。 理想情况下, IEquatable<T>在类型中实现IEquatable<T> ,特别是如果它是值类型。 这些方法将由默认的相等比较器调用。

请注意,对于有序比较而言,这一切都没有 - 这是有道理的,因为在某些情况下您可以轻松指定相等但不是总排序。 这基本上与Dictionary<TKey, TValue>完全相同。

如果你想要一个使用排序而不仅仅是相等比较的集合,你应该使用.NET 4中的SortedSet<T> - 它允许你指定IComparer<T>而不是IEqualityComparer<T> 这将使用IComparer<T>.Compare - 如果您使用的是Comparer<T>.Default ,它将委托给IComparable<T>.CompareToIComparable.CompareTo

以下是对未提及的答案的一部分的澄清: HashSet<T>的对象类型不必实现IEqualityComparer<T> ,而只需要重写Object.GetHashCode()Object.Equals(Object obj)

而不是这个:

public class a : IEqualityComparer<a>
{
  public int GetHashCode(a obj) { /* Implementation */ }
  public bool Equals(a obj1, a obj2) { /* Implementation */ }
}

你做这个:

public class a
{
  public override int GetHashCode() { /* Implementation */ }
  public override bool Equals(object obj) { /* Implementation */ }
}

这很微妙,但这让我感到震惊,因为我想要让HashSet按照预期的方式运行。 和其他人一样, HashSet<a>将在使用集合时根据需要调用a.GetHashCode()a.Equals(obj)

HashSet使用EqualsGetHashCode()

CompareTo适用于有序集。

如果您想要唯一对象,但不关心它们的迭代顺序, HashSet<T>通常是最佳选择。

构造函数HashSet接收对象,用于添加新对象的IEqualityComparer实现。 如果您在HashSet中使用方法,则必须覆盖Equals,GetHashCode

namespace HashSet
{
    public class Employe
    {
        public Employe() {
        }

        public string Name { get; set; }

        public override string ToString()  {
            return Name;
        }

        public override bool Equals(object obj) {
            return this.Name.Equals(((Employe)obj).Name);
        }

        public override int GetHashCode() {
            return this.Name.GetHashCode();
        }
    }

    class EmployeComparer : IEqualityComparer<Employe>
    {
        public bool Equals(Employe x, Employe y)
        {
            return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower());
        }

        public int GetHashCode(Employe obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer());
            hashSet.Add(new Employe() { Name = "Nik" });
            hashSet.Add(new Employe() { Name = "Rob" });
            hashSet.Add(new Employe() { Name = "Joe" });
            Display(hashSet);
            hashSet.Add(new Employe() { Name = "Rob" });
            Display(hashSet);

            HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer());
            hashSetB.Add(new Employe() { Name = "Max" });
            hashSetB.Add(new Employe() { Name = "Solomon" });
            hashSetB.Add(new Employe() { Name = "Werter" });
            hashSetB.Add(new Employe() { Name = "Rob" });
            Display(hashSetB);

            var union = hashSet.Union<Employe>(hashSetB).ToList();
            Display(union);
            var inter = hashSet.Intersect<Employe>(hashSetB).ToList();
            Display(inter);
            var except = hashSet.Except<Employe>(hashSetB).ToList();
            Display(except);

            Console.ReadKey();
        }

        static void Display(HashSet<Employe> hashSet)
        {
            if (hashSet.Count == 0)
            {
                Console.Write("Collection is Empty");
                return;
            }
            foreach (var item in hashSet)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }

        static void Display(List<Employe> list)
        {
            if (list.Count == 0)
            {
                Console.WriteLine("Collection is Empty");
                return;
            }
            foreach (var item in list)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM