HashSet은 요소와 동등성을 어떻게 비교합니까?


127

나는 수업이 있습니다 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.count있다 2,하지만 :

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>생성자에 구현을 추가 하거나 클래스에서 구현하십시오 a. msdn.microsoft.com/ko-kr/library/bb301504(v=vs.110).aspx
Jaider

답변:


137

그것은을 사용 IEqualityComparer<T>( EqualityComparer<T>.Default당신이 건축에 다른 하나를 지정하지 않은 경우).

세트에 요소를 추가하면을 사용하여 해시 코드를 찾고 IEqualityComparer<T>.GetHashCode해시 코드와 요소를 모두 저장합니다 (물론 요소가 이미 세트에 있는지 확인한 후).

요소를 찾으려면 먼저를 사용하여 IEqualityComparer<T>.GetHashCode해시 코드를 찾은 다음 동일한 해시 코드를 가진 모든 요소에 IEqualityComparer<T>.Equals대해 실제 평등을 비교 하는 데 사용 합니다.

즉, 두 가지 옵션이 있습니다.

  • IEqualityComparer<T>생성자에 사용자 정의 를 전달하십시오. 이 옵션은 T자체를 수정할 수 없거나 기본이 아닌 동등 관계를 원하는 경우에 가장 적합한 옵션입니다 (예 : "음의 사용자 ID를 가진 모든 사용자는 동일하게 간주됩니다"). 이것은 거의 유형 자체 에서 Foo구현되지 않으며 (즉, 구현하지 않음 IEqualityComparer<Foo>) 비교 전용으로 사용되는 별도의 유형입니다.
  • GetHashCode및 을 재정 의하여 형식 자체에서 동등성을 구현 Equals(object)합니다. 이상적으로는 IEquatable<T>특히 값 형식 인 경우 형식으로 구현 하십시오. 이러한 메소드는 기본 동등성 비교기에 의해 호출됩니다.

평등을 쉽게 지정할 수는 있지만 전체 순서는 지정할 수없는 상황이 있기 때문에이 중 어느 것도 순서 비교와 관련이 없습니다. 이것은 Dictionary<TKey, TValue>기본적으로 모두 동일 합니다.

등식 비교 대신 순서 를 사용하는 집합을 원한다면 SortedSet<T>.NET 4에서 사용해야 IComparer<T>합니다 IEqualityComparer<T>. 이것은 IComparer<T>.Compare-를 사용 IComparable<T>.CompareTo하거나 위임하는 IComparable.CompareTo경우 사용 Comparer<T>.Default합니다.


7
+1 또한 노트 @ 활용할 수있는 간단한 방법은 말했다 지적 (IMO 코멘트 여기해야한다는) tyriker의 답변을 IEqualityComparer<T>.GetHashCode/Equals()구현하는 것입니다 EqualsGetHashCodeT자신을 (그리고 당신이 그 일을하는 동안, 당신은 또한 강력한 형식의 대응을 구현하는 것 : - bool IEquatable<T>.Equals(T other))
루벤 Bartelink

5
이 명확하게 간단한 경우에 대한 오버 라이딩 상태를하지 않기 때문에 매우 정확하지만이 답변이 다소 특히 새로운 사용자를위한 혼동 될 수 있습니다 EqualsGetHashCode@의 tyriker의 대답에 언급 한 바와 같이 - 충분하다.
BartoszKP

Imo를 구현하면 IComparable(또는 IComparer그 문제에 대해) 평등을 별도로 구현하라는 요청을받지 않아야합니다 (단지 GetHashCode). 어떤 의미에서 비교 인터페이스는 동등 인터페이스에서 상속해야합니다. 나는 두 가지 별도의 기능 (여기서 무언가가 같거나 그렇지 않다고 말하면 별도로 평등을 최적화 할 수 있음)을 갖는 성능 이점을 이해하지만 여전히 인스턴스가 CompareTo기능 이 동일 하고 프레임 워크 가 동일하지 않을 때 지정하면 매우 혼란 스럽습니다. 그.
nawfal

@nawfal 모든 것이 논리적 순서를 갖는 것은 아닙니다. bool 속성을 포함하는 두 가지를 비교 a.boolProp == b.boolProp ? 1 : 0하는 경우 a.boolProp == b.boolProp ? 0 : -1또는 같은 또는 무언가를 작성 해야하는 것은 끔찍합니다 a.boolProp == b.boolProp ? 1 : -1. 수다!
Simon_Weaver

1
@Simon_Weaver입니다. 내가 제안한 가상의 기능에서 어떻게 든 피하고 싶습니다.
nawfal

77

말하지 남아있어 해답의 일부에 다음의 설명은 : 당신의 목표 유형은 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)필요에 따라 설정 작업을 할 때.


2
좋은 지적. @JonSkeet의 답변에 대한 내 의견에서 언급 한 바와 같이 BTW bool IEquatable<T>.Equals(T other)는 약간의 효율성 향상을 위해 구현해야 하지만 더 중요한 선명도 이점을 구현해야합니다 . 구현의 필요성에 추가 OBV 이유를 들어 GetHashCode함께 IEquatable<T>, IEquatable <T>에 대한 문서는 일관성을 위해 당신은 또한 오버라이드 (override) 할 필요가 있음을 언급 object.Equals일관성
루벤 Bartelink에게

나는 이것을 구현하려고 시도했다. ovveride getHashcode작동하지만 override bool equals오류를 얻을 수는 : 어떤 방법은 재정에 찾을 수 없습니다. 어떤 생각?
Stefanvds

마지막으로 내가 찾던 정보. 감사합니다.
Mauro Sampietro

위의 대답에 내 의견에서 - 귀하의 경우 "대신"에서, 당신은 할 수있는 public class a : IEqualityComparer<a> {다음과 new HashSet<a>(a).
HankCa

그러나 위의 Jon Skeets 의견을 참조하십시오.
HankCa

9

HashSet사용 Equals하고 GetHashCode().

CompareTo 주문 세트입니다.

고유 한 객체를 원하지만 반복 순서에 신경 쓰지 않는 HashSet<T>경우 일반적으로 최선의 선택입니다.


5

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

이름이 null이면 어떻게 되나요? null의 해시 값은 무엇입니까?
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.