두 문자열을 모두 교환할 수 있는 경우 두 문자열이 있는 구조에 대해 GetHashCode를 어떻게 구현합니까?
문제
C#에는 다음과 같은 구조가 있습니다.
public struct UserInfo
{
public string str1
{
get;
set;
}
public string str2
{
get;
set;
}
}
유일한 규칙은 UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))
이 구조에 대해 GetHashCode 함수를 재정의하는 방법은 무엇입니까?
해결책
MSDN:
해시 함수에는 다음 속성이 있어야 합니다.
- 두 객체가 동일한 것으로 비교되면,
GetHashCode
각 객체에 대한 메서드는 동일한 값을 반환해야 합니다.그러나 두 객체가 동일하다고 비교되지 않는 경우GetHashCode
두 개체에 대한 메서드는 서로 다른 값을 반환할 필요가 없습니다.- 그만큼
GetHashCode
객체의 메서드는 객체의 반환 값을 결정하는 객체 상태에 대한 수정이 없는 한 일관되게 동일한 해시 코드를 반환해야 합니다.Equals
방법.이는 현재 애플리케이션을 실행하는 경우에만 해당되며, 애플리케이션을 다시 실행하면 다른 해시 코드가 반환될 수 있습니다.- 최상의 성능을 위해서는 해시 함수가 모든 입력에 대해 무작위 분포를 생성해야 합니다.
올바른 방법을 고려하는 것은 다음과 같습니다.
return str1.GetHashCode() ^ str2.GetHashCode()
^
다른 교환 연산으로 대체 가능
다른 팁
보다 존 스키트의 답변 - 다음과 같은 이진 연산 ^
좋지 않기 때문에 종종 충돌 해시가 생성됩니다!
public override int GetHashCode()
{
unchecked
{
return (str1 ?? String.Empty).GetHashCode() +
(str2 ?? String.Empty).GetHashCode();
}
}
'^'를 사용하는 것보다 '+' 연산자를 사용하는 것이 더 나을 수 있습니다. 왜냐하면 ('AA', 'BB')와 ('BB', 'AA')가 명시적으로 동일하기를 명시적으로 원하지만 ( 'AA', 'AA') 및 ('BB', 'BB')는 동일합니다(또는 해당 문제에 대한 모든 동일한 쌍).
이 솔루션에서는 '가능한 한 빨리' 규칙을 완전히 준수하지 않습니다. 왜냐하면 null의 경우 알려진 상수를 즉시 반환하는 대신 빈 문자열에 대해 'GetHashCode()'를 수행하기 때문입니다. 그러나 명시적으로 측정하지 않더라도 기꺼이 할 것입니다. 많은 null을 기대하지 않는 한 걱정할 만큼 차이가 크지 않을 것이라고 추측할 위험이 있습니다.
일반적으로 클래스에 대한 해시 코드를 생성하는 간단한 방법은 해시 코드 생성에 참여할 수 있는 모든 데이터 필드를 XOR하는 것입니다(다른 사람이 지적한 대로 null을 확인하도록 주의).이는 또한 UserInfo("AA", "BB") 및 UserInfo("BB", "AA")의 해시 코드가 동일해야 하는 (인공적인?) 요구 사항을 충족합니다.
클래스 사용에 대해 가정할 수 있다면 해시 함수를 향상시킬 수 있습니다.예를 들어 str1과 str2가 동일한 경우가 일반적이라면 XOR은 좋은 선택이 아닐 수 있습니다.그러나 str1과 str2가 이름과 성을 나타내는 경우 XOR이 아마도 좋은 선택일 것입니다.
이것이 실제 사례가 아니라는 것은 분명하지만 다음 사항을 지적할 가치가 있습니다.- 이것은 아마도 구조체 사용의 좋지 않은 예일 것입니다.구조체는 일반적으로 값 의미 체계를 가져야 하지만 여기서는 그렇지 않은 것 같습니다.- 해시 코드를 생성하기 위해 setter와 함께 속성을 사용하는 것도 문제를 야기합니다.
간단한 일반적인 방법은 다음과 같습니다.
return string.Format("{0}/{1}", str1, str2).GetHashCode();
엄격한 성능 요구 사항이 없는 한 이것이 제가 생각할 수 있는 가장 쉬운 방법이며 복합 키가 필요할 때 이 방법을 자주 사용합니다.이는 다음을 처리합니다. null
경우에는 문제가 없으며 (일반적으로) 해시 충돌이 발생하지 않습니다.문자열에 '/'가 필요한 경우 예상하지 못한 다른 구분 기호를 선택하세요.
public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}
ReSharper가 제안하는 내용을 따르면 다음과 같습니다.
public int GetHashCode()
{
unchecked
{
int hashCode;
// String properties
hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);
// int properties
hashCode = (hashCode * 397) ^ intProperty;
return hashCode;
}
}
397은 결과 변수가 오버플로되고 해시 비트가 어느 정도 혼합되도록 하기에 충분한 크기의 소수로, 해시 코드의 더 나은 배포를 제공합니다.그렇지 않으면 397에는 같은 크기의 다른 소수와 구별되는 특별한 것이 없습니다.
아, 그렇습니다. Gary Shutler가 지적했듯이:
return str1.GetHashCode() + str2.GetHashCode();
넘칠 수 있습니다.Artem이 제안한 대로 캐스팅을 시도하거나 unchecked 키워드로 명령문을 둘러쌀 수 있습니다.
return unchecked(str1.GetHashCode() + str2.GetHashCode());
이것을 시도해 보세요:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
많은 가능성.예:
return str1.GetHashCode() ^ str1.GetHashCode()
아마도 str1.GetHashCode() + str2.GetHashCode()와 같은 것일까요?또는 (str1.GetHashCode() + str2.GetHashCode()) / 2?이렇게 하면 str1과 str2가 바뀌는지 여부에 관계없이 동일하게 됩니다....
정렬한 다음 연결합니다.
return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1) .GetHashCode();
GetHashCode의 결과는 다음과 같습니다.
- 최대한 빨리.
- 가능한 한 독특합니다.
이를 염두에 두고 다음과 같이 하겠습니다.
if (str1 == null)
if (str2 == null)
return 0;
else
return str2.GetHashCode();
else
if (str2 == null)
return str1.GetHashCode();
else
return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();
편집하다: null을 잊어버렸습니다.코드가 수정되었습니다.
너무 복잡하고 null 등을 잊어버립니다.이는 버킷팅과 같은 작업에 사용되므로 다음과 같은 작업을 수행할 수 있습니다.
if (null != str1) {
return str1.GetHashCode();
}
if (null != str2) {
return str2.GetHashCode();
}
//Not sure what you would put here, some constant value will do
return 0;
이는 str1이 비정상적으로 많은 비율의 인스턴스에서 일반적이지 않을 가능성이 높다고 가정하여 편향된 것입니다.