HashSet 如何比较元素是否相等?

发布于 2024-12-28 16:27:04 字数 889 浏览 3 评论 0原文

我有一个 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< /code>,但是:

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 是拥有唯一对象列表的最佳方式吗?

I have a class that is 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);
    }
}

When I add a list of object of this class to a hash set:

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);

Everything is fine and ha.count is 2, but:

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));

Now ha.count is 3.

  1. Why doesn't HashSet respect a's CompareTo method.
  2. Is HashSet the best way to have a list of unique objects?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

青衫儰鉨ミ守葔 2025-01-04 16:27:04

它使用 IEqualityComparer( EqualityComparer.Default除非您在构造时指定不同的 EqualityComparer.Default。

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

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

这意味着您有两个选择:

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

请注意,这一切都不是按有序比较的方式进行的 - 这是有道理的,因为在某些情况下,您可以轻松指定相等性,但不能指定总排序。基本上,这与 Dictionary 相同。

如果您想要一个使用排序而不仅仅是相等比较的集合,则应该使用 SortedSet- 允许您指定 IComparer 而不是一个 IEqualityComparer。这将使用 IComparer.Compare - 它将委托给 IComparable.CompareToIComparable.CompareTo(如果您使用的是) 比较器。默认

It uses an IEqualityComparer<T> (EqualityComparer<T>.Default unless you specify a different one on construction).

When you add an element to the set, it will find the hash code using IEqualityComparer<T>.GetHashCode, and store both the hash code and the element (after checking whether the element is already in the set, of course).

To look an element up, it will first use the IEqualityComparer<T>.GetHashCode to find the hash code, then for all elements with the same hash code, it will use IEqualityComparer<T>.Equals to compare for actual equality.

That means you have two options:

  • Pass a custom IEqualityComparer<T> into the constructor. This is the best option if you can't modify the T itself, or if you want a non-default equality relation (e.g. "all users with a negative user ID are considered equal"). This is almost never implemented on the type itself (i.e. Foo doesn't implement IEqualityComparer<Foo>) but in a separate type which is only used for comparisons.
  • Implement equality in the type itself, by overriding GetHashCode and Equals(object). Ideally, implement IEquatable<T> in the type as well, particularly if it's a value type. These methods will be called by the default equality comparer.

Note how none of this is in terms of an ordered comparison - which makes sense, as there are certainly situations where you can easily specify equality but not a total ordering. This is all the same as Dictionary<TKey, TValue>, basically.

If you want a set which uses ordering instead of just equality comparisons, you should use SortedSet<T> from .NET 4 - which allows you to specify an IComparer<T> instead of an IEqualityComparer<T>. This will use IComparer<T>.Compare - which will delegate to IComparable<T>.CompareTo or IComparable.CompareTo if you're using Comparer<T>.Default.

世俗缘 2025-01-04 16:27:04

以下是对未提及的部分答案的澄清:HashSet 的对象类型不必实现 IEqualityComparer,而只需实现覆盖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.GetHashCode()a.Equals(obj)与该集一起工作。

Here's clarification on a part of the answer that's been left unsaid: The object type of your HashSet<T> doesn't have to implement IEqualityComparer<T> but instead just has to override Object.GetHashCode() and Object.Equals(Object obj).

Instead of this:

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

You do this:

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

It is subtle, but this tripped me up for the better part of a day trying to get HashSet to function the way it is intended. And like others have said, HashSet<a> will end up calling a.GetHashCode() and a.Equals(obj) as necessary when working with the set.

↙厌世 2025-01-04 16:27:04

HashSet 使用 EqualsGetHashCode()

CompareTo 用于有序集。

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

HashSet uses Equals and GetHashCode().

CompareTo is for ordered sets.

If you want unique objects, but you don't care about their iteration order, HashSet<T> is typically the best choice.

伪装你 2025-01-04 16:27:04

我来这里寻找答案,但发现所有答案的信息太多或不够,所以这是我的答案...

既然您创建了一个自定义类,您需要实现 GetHashCode等于。在此示例中,我将使用类 Student 而不是 a,因为它更易于遵循并且不违反任何命名约定。 这是实现的样子

public override bool Equals(object obj)
{
    return obj is Student student && Id == student.Id;
}

public override int GetHashCode()
{
    return HashCode.Combine(Id);
}

我偶然发现Microsoft 的这篇文章 提供了一种非常简单的方法来实现这些(如果您使用的是 Visual Studio)。如果对其他人有帮助,请参阅以下使用 Visual Studio 在 HashSet 中使用自定义数据类型的完整步骤:

给定一个具有 2 个简单属性和一个初始值设定项的类 Student

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

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

要实现 IComparable,请添加 : IComparable 像这样:

public class Student : IComparable<Student>

您将看到一个红色的波浪线出现,并显示一条错误消息,表明您的类没有实现 IComparable。单击建议或按 Alt+Enter 并使用建议来实施它。

使用建议实现 IComparable

您将看到生成的方法。然后,您可以编写自己的实现,如下所示:

public int CompareTo(Student student)
{
    return this.Id.CompareTo(student.Id);
}

在上面的实现中,仅比较 Id 属性,忽略 name。接下来,右键单击代码并选择“快速操作和重构”,然后选择“生成等于和 GetHashCode”

Generate Equals and GetHashCode

将弹出一个窗口,您可以在其中选择可以选择用于散列的属性,甚至可以实现 IEquitable(如果您愿意):

弹出窗口,您可以在其中选择要用于散列的属性

这是生成的代码:

public class Student : IComparable<Student>, IEquatable<Student> {
    ...
    public override bool Equals(object obj)
    {
        return Equals(obj as Student);
    }

    public bool Equals(Student other)
    {
        return other != null && Id == other.Id;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Id);
    }
}

现在,如果您尝试添加如图所示的重复项下面的内容将被跳过:

static void Main(string[] args)
{
    Student s1 = new Student(1);
    Student s2 = new Student(2);
    HashSet<Student> hs = new HashSet<Student>();

    hs.Add(s1);
    hs.Add(s2);
    hs.Add(new Student(1)); //will be skipped
    hs.Add(new Student(3));
}

您现在可以像这样使用 .Contains

for (int i = 0; i <= 4; i++)
{
    if (hs.Contains(new Student(i)))
    {
        Console.WriteLine($@"Set contains student with Id {i}");
    }
    else
    {
        Console.WriteLine($@"Set does NOT contain a student with Id {i}");
    }
}

输出:

控制台输出

I came here looking for answers, but found that all the answers had too much info or not enough, so here is my answer...

Since you've created a custom class you need to implement GetHashCode and Equals. In this example I will use a class Student instead of a because it's easier to follow and doesn't violate any naming conventions. Here is what the implementations look like:

public override bool Equals(object obj)
{
    return obj is Student student && Id == student.Id;
}

public override int GetHashCode()
{
    return HashCode.Combine(Id);
}

I stumbled across this article from Microsoft that gives an incredibly easy way to implement these if you're using Visual Studio. In case it's helpful to anyone else, here are complete steps for using a custom data type in a HashSet using Visual Studio:

Given a class Student with 2 simple properties and an initializer

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

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

To Implement IComparable, add : IComparable<Student> like so:

public class Student : IComparable<Student>

You will see a red squiggly appear with an error message saying your class doesn't implement IComparable. Click on suggestions or press Alt+Enter and use the suggestion to implement it.

use the suggestion to implement IComparable

You will see the method generated. You can then write your own implementation like below:

public int CompareTo(Student student)
{
    return this.Id.CompareTo(student.Id);
}

In the above implementation only the Id property is compared, name is ignored. Next right-click in your code and select Quick actions and refactorings, then Generate Equals and GetHashCode

Generate Equals and GetHashCode

A window will pop up where you can select which properties to use for hashing and even implement IEquitable if you'd like:

pop up where you can select which properties to use for hashing

Here is the generated code:

public class Student : IComparable<Student>, IEquatable<Student> {
    ...
    public override bool Equals(object obj)
    {
        return Equals(obj as Student);
    }

    public bool Equals(Student other)
    {
        return other != null && Id == other.Id;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Id);
    }
}

Now if you try to add a duplicate item like shown below it will be skipped:

static void Main(string[] args)
{
    Student s1 = new Student(1);
    Student s2 = new Student(2);
    HashSet<Student> hs = new HashSet<Student>();

    hs.Add(s1);
    hs.Add(s2);
    hs.Add(new Student(1)); //will be skipped
    hs.Add(new Student(3));
}

You can now use .Contains like so:

for (int i = 0; i <= 4; i++)
{
    if (hs.Contains(new Student(i)))
    {
        Console.WriteLine($@"Set contains student with Id {i}");
    }
    else
    {
        Console.WriteLine($@"Set does NOT contain a student with Id {i}");
    }
}

Output:

Console output

谜兔 2025-01-04 16:27:04

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");
        }
    }
}

HashSet<T> constructor receive object what implement IEqualityComparer<T> for adding new object. If you want to use method in HashSet you need to overrride Equals() and 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");
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文