Làm thế nào để Hashset so sánh các yếu tố cho bình đẳng?


127

Tôi có một lớp học là 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);
    }
}

Khi tôi thêm một danh sách các đối tượng của lớp này vào một bộ băm:

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

Mọi thứ đều ổn và ha.count2, nhưng:

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

Bây giờ ha.count3.

  1. Tại sao không HashSettôn trọng a's CompareTophương pháp.
  2. HashSetcách tốt nhất để có một danh sách các đối tượng độc đáo?

Thêm một triển khai IEqualityComparer<T>trong hàm tạo hoặc thực hiện nó trong lớp a. msdn.microsoft.com/en-us/l
Library / bb495504 (v = vs.110) .aspx

Câu trả lời:


137

Nó sử dụng một IEqualityComparer<T>( EqualityComparer<T>.Defaulttrừ khi bạn chỉ định một cái khác trong xây dựng).

Khi bạn thêm một phần tử vào tập hợp, nó sẽ tìm mã băm bằng cách sử dụng IEqualityComparer<T>.GetHashCodevà lưu trữ cả mã băm và phần tử (tất nhiên sau khi kiểm tra xem phần tử đó đã có trong tập chưa, tất nhiên).

Để tìm một phần tử, trước tiên, nó sẽ sử dụng IEqualityComparer<T>.GetHashCodeđể tìm mã băm, sau đó cho tất cả các phần tử có cùng mã băm, nó sẽ sử dụng IEqualityComparer<T>.Equalsđể so sánh cho sự bình đẳng thực tế.

Điều đó có nghĩa là bạn có hai lựa chọn:

  • Truyền một tùy chỉnh IEqualityComparer<T>vào các nhà xây dựng. Đây là tùy chọn tốt nhất nếu bạn không thể sửa đổi Tchính nó hoặc nếu bạn muốn có mối quan hệ bình đẳng không mặc định (ví dụ: "tất cả người dùng có ID người dùng âm được coi là bằng nhau"). Điều này gần như không bao giờ được thực hiện trên chính loại đó (tức là Fookhông thực hiện IEqualityComparer<Foo>) nhưng trong một loại riêng biệt chỉ được sử dụng để so sánh.
  • Thực hiện bình đẳng trong chính loại, bằng cách ghi đè GetHashCodeEquals(object). Lý tưởng nhất là thực hiện IEquatable<T>theo kiểu, đặc biệt nếu đó là loại giá trị. Các phương thức này sẽ được gọi bằng bộ so sánh đẳng thức mặc định.

Lưu ý rằng không có gì trong số này là về mặt so sánh theo thứ tự - điều này có ý nghĩa, vì chắc chắn có những tình huống mà bạn có thể dễ dàng xác định đẳng thức nhưng không phải là tổng thứ tự. Điều này hoàn toàn giống như Dictionary<TKey, TValue>, về cơ bản.

Nếu bạn muốn một tập hợp sử dụng thứ tự thay vì chỉ so sánh bằng, bạn nên sử dụng SortedSet<T>từ .NET 4 - cho phép bạn chỉ định một IComparer<T>thay vì một IEqualityComparer<T>. Điều này sẽ sử dụng IComparer<T>.Compare- sẽ ủy thác IComparable<T>.CompareTohoặc IComparable.CompareTonếu bạn đang sử dụng Comparer<T>.Default.


7
+1 Cũng lưu ý câu trả lời của @ tyriker (IMO nên là một nhận xét ở đây) chỉ ra rằng cách đơn giản nhất để tận dụng nói IEqualityComparer<T>.GetHashCode/Equals()là thực hiện EqualsGetHashCodetrên Tchính nó (và trong khi bạn đang làm điều đó, bạn cũng sẽ thực hiện đối tác được gõ mạnh : - bool IEquatable<T>.Equals(T other))
Ruben Bartelink

5
Mặc dù rất chính xác, câu trả lời này có thể hơi khó hiểu, đặc biệt là đối với người dùng mới vì nó không nêu rõ rằng đối với trường hợp đơn giản nhất ghi đè EqualsGetHashCodelà đủ - như đã đề cập trong câu trả lời của @ tyriker.
BartoszKP

Imo một khi bạn thực hiện IComparable(hoặc IComparercho vấn đề đó), bạn không nên được yêu cầu thực hiện bình đẳng một cách riêng biệt (nhưng chỉ GetHashCode). Theo một nghĩa nào đó, các giao diện so sánh nên kế thừa từ các giao diện bình đẳng. Tôi hiểu các lợi ích hiệu suất khi có hai hàm riêng biệt (trong đó bạn có thể tối ưu hóa sự bình đẳng một cách riêng biệt chỉ bằng cách nói nếu một cái gì đó bằng nhau hay không) nhưng vẫn rất khó hiểu khi bạn đã chỉ định khi các trường hợp bằng nhau về CompareTochức năng và khung sẽ không xem xét cái đó.
nawfal

@nawfal không phải mọi thứ đều có thứ tự hợp lý. nếu bạn đang so sánh hai thứ có chứa một thuộc tính bool thì thật đơn giản khi phải viết một cái gì đó giống a.boolProp == b.boolProp ? 1 : 0hoặc nên a.boolProp == b.boolProp ? 0 : -1hay a.boolProp == b.boolProp ? 1 : -1. Yuk!
Simon_Weaver

1
@Simon_Weaver nó là. Tôi muốn tránh bằng cách nào đó trong tính năng giả định của tôi, tôi đã đề xuất.
nawfal

77

Dưới đây là một phần của câu trả lời chưa được trả lời: Loại đối tượng của bạn HashSet<T>không phải thực hiện IEqualityComparer<T>mà thay vào đó chỉ phải ghi đè Object.GetHashCode()Object.Equals(Object obj).

Thay vì điều này:

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

Bạn làm điều này:

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

Thật là tinh tế, nhưng điều này đã làm tôi vấp ngã trong phần tốt hơn của một ngày cố gắng để Hashset hoạt động theo cách nó được dự định. Và như những người khác đã nói, HashSet<a>cuối cùng sẽ gọi a.GetHashCode()a.Equals(obj)khi cần thiết khi làm việc với bộ này.


2
Điểm tốt. BTW như đã đề cập trong nhận xét của tôi về câu trả lời của @ JonSkeet, bạn cũng nên thực hiện bool IEquatable<T>.Equals(T other)để đạt được hiệu quả nhẹ nhưng quan trọng hơn là lợi ích rõ ràng. Vì lý do obv, ngoài nhu cầu thực hiện GetHashCodecùng với IEquatable<T>, tài liệu cho IEquitable <T> đề cập rằng vì mục đích nhất quán, bạn cũng nên ghi đè lên tính object.Equalsnhất quán
Ruben Bartelink

Tôi đã thử thực hiện điều này. Các ovveride getHashcodecông trình, nhưng override bool equalsnhận được lỗi: không tìm thấy phương pháp nào để ghi đè. bất kỳ ý tưởng?
Stefanvds

Cuối cùng, thông tin tôi đang tìm kiếm. Cảm ơn bạn.
Mauro Sampietro

Từ nhận xét của tôi về câu trả lời trên - Trong trường hợp "Thay vì" của bạn, bạn có thể có public class a : IEqualityComparer<a> {, và sau đó new HashSet<a>(a).
HankCa

Nhưng xem ý kiến ​​của Jon Skeets ở trên.
HankCa

9

HashSetsử dụng EqualsGetHashCode().

CompareTo là cho các bộ đặt hàng.

Nếu bạn muốn các đối tượng độc đáo, nhưng bạn không quan tâm đến thứ tự lặp lại của chúng, HashSet<T>thường là lựa chọn tốt nhất.


5

hàm tạo Hashset nhận đối tượng triển khai IEqualityComparer để thêm đối tượng mới. nếu bạn sử dụng phương thức trong Hashset, bạn sẽ ghi đè bằng, 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");
        }
    }
}

Nếu tên là null thì sao? giá trị băm của null là gì?
joe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.