isEqual : 및 해시를 재정의하는 모범 사례


267

isEqual:Objective-C에서 어떻게 올바르게 재정의 합니까? "캐치"는 두 개의 객체가 동일한 경우 ( isEqual:방법에 의해 결정된 경우) 동일한 해시 값을 가져야합니다.

Cocoa Fundamentals GuideIntrospection 섹션에는 다음과 같은 클래스 를 재정의하는 방법에 대한 예제가 있습니다 .isEqual:MyWidget

- (BOOL)isEqual:(id)other {
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:other];
}

- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
    if (self == aWidget)
        return YES;
    if (![(id)[self name] isEqual:[aWidget name]])
        return NO;
    if (![[self data] isEqualToData:[aWidget data]])
        return NO;
    return YES;
}

포인터 동등성을 검사 한 다음 클래스 동등성을 검사하고 마지막으로 및 속성 isEqualToWidget:만 검사하는를 사용하여 객체를 비교합니다 . 예제에서 보이지 않는 것은 override하는 방법 입니다.namedatahash

평등에 영향을 미치지 않는 다른 속성이 있다고 가정 해 봅시다 age. 안 hash메소드는 그런 오버라이드 (override) 할 수 namedata해시 영향을 줍니까? 그렇다면 어떻게 하시겠습니까? 해시를 추가 name하고 data? 예를 들면 다음과 같습니다.

- (NSUInteger)hash {
    NSUInteger hash = 0;
    hash += [[self name] hash];
    hash += [[self data] hash];
    return hash;
}

충분합니까? 더 나은 기술이 있습니까? 프리미티브가 있다면 int? NSNumber해시를 얻기 위해 변환 ? 아니면 같은 구조체 NSRect?

( Brain fart : 원래 "bitwise OR"과 함께 썼습니다 |=. Meant add.)


2
if (![other isKindOfClass:[self class]])-이것은 기술적으로 평등이 Commutative가 아님을 의미합니다. 즉 A = B는 B = A를 의미하지 않습니다 (예 : 하나가 다른 클래스의 하위 클래스 인 경우)
Robert

문서 링크가 죽은 지금에 보관입니다 성찰
jedwidz

답변:


111

로 시작

 NSUInteger prime = 31;
 NSUInteger result = 1;

그런 다음 모든 기본 요소에 대해

 result = prime * result + var

객체의 경우 nil에 0을 사용하고 그렇지 않으면 해시 코드를 사용하십시오.

 result = prime * result + [var hash];

부울의 경우 두 개의 다른 값을 사용합니다

 result = prime * result + ((var)?1231:1237);

설명 및 속성

이것은 tcurdt의 작품이 아니며 의견은 더 많은 설명을 요구했기 때문에 기여에 대한 편집은 공정하다고 생각합니다.

이 알고리즘은 "Effective Java"책에서 대중화되었으며 관련 장은 현재 온라인에서 찾을 수 있습니다 . 이 책은 알고리즘을 대중화했으며 현재는 많은 Java 응용 프로그램 (Eclipse 포함)에서 기본값입니다. 그러나 Dan Bernstein 또는 Chris Torek에 의해 다양한 오래된 구현에서 파생되었습니다. 그 오래된 알고리즘은 원래 Usenet에서 떠 다니고 있으며 특정 속성은 어렵습니다. 예를 들어, 이 Apache 코드 에는 원래 소스를 참조하는 흥미로운 주석이 있습니다 (이름 검색).

결론적으로 이것은 매우 오래된 간단한 해싱 알고리즘입니다. 가장 성능이 우수하지 않으며 수학적으로 "좋은"알고리즘으로 입증되지도 않았습니다. 그러나 그것은 간단하고 많은 사람들이 좋은 결과로 오랫동안 그것을 사용했기 때문에 많은 역사적 지원을받습니다.


9
1231 : 1237은 어디에서 왔습니까? Java의 Boolean.hashCode ()에서도 볼 수 있습니다. 마법인가요?
David Leonard

17
충돌이 발생하는 것은 해싱 알고리즘의 특성입니다. 그래서 나는 당신의 요점을 보지 못합니다, 폴
tcurdt

85
내 의견으로는이 답변은 실제 질문 (NSObject의 해시를 재정의하는 모범 사례)에 응답하지 않습니다. 하나의 특정 해시 알고리즘 만 제공합니다. 게다가, 설명의 희소성으로 인해 문제에 대한 깊은 지식 없이는 이해하기가 어려워지고 사람들은 자신이 무엇을하고 있는지 알지 못하고이를 사용할 수 있습니다. 나는 왜이 질문이 너무 많은 투표를했는지 이해할 수 없습니다.
Ricardo Sanchez-Saez

6
첫 번째 문제-(int)는 작고 오버플로하기 쉽습니다. NSUInteger를 사용하십시오. 두 번째 문제-결과에 각 변수 해시를 계속 곱하면 결과가 오버플로됩니다. 예. [NSString 해시]는 큰 값을 만듭니다. 5 개 이상의 변수가있는 경우이 알고리즘으로 오버플로하기 쉽습니다. 모든 것이 동일한 해시에 매핑되어 결과가 나빠질 수 있습니다. 내 답변보기 : stackoverflow.com/a/4393493/276626
Paul Solt

10
@PaulSolt-오버플로는 해시 생성에 문제가 아니며 충돌이 발생합니다. 그러나 오버플로가 반드시 충돌 가능성을 높이는 것은 아니며 모든 것이 동일한 해시에 매핑되도록하는 오버플로에 대한 진술은 단순히 잘못된 것입니다.
DougW 2016 년

81

나는 Objective-C를 직접 집어 들고 있기 때문에 특정 언어를 구체적으로 말할 수는 없지만 다른 언어에서는 두 인스턴스가 "동일"한 경우 동일한 해시를 반환해야합니다. 그렇지 않으면 모두 가질 것입니다 해시 테이블 (또는 사전 유형 컬렉션)에서 키로 사용하려고 할 때 문제가 발생합니다.

반면에 2 개의 인스턴스가 같지 않으면 동일한 해시를 갖거나 갖지 않을 수 있습니다. 그렇지 않으면 가장 좋습니다. 이것은 해시 테이블에서 O (1) 검색과 O (N) 검색의 차이점입니다. 모든 해시가 충돌하면 테이블 검색이 목록을 검색하는 것보다 낫다는 것을 알 수 있습니다.

모범 사례와 관련하여 해시는 입력 값에 대한 임의의 값 분포를 반환해야합니다. 즉, 예를 들어, double이 있지만 대부분의 값이 0과 100 사이에 클러스터되는 경향이있는 경우 해당 값에서 반환 된 해시가 가능한 전체 해시 값 범위에 고르게 분포되어 있는지 확인해야합니다. . 이렇게하면 성능이 크게 향상됩니다.

여기에 나열된 몇 가지를 포함하여 많은 해싱 알고리즘이 있습니다. 성능에 큰 영향을 미칠 수 있으므로 새 해시 알고리즘을 만들지 않기 위해 기존 해시 방법을 사용하고 예제에서와 같이 일종의 비트 조합을 수행하는 것이 피하는 좋은 방법입니다.


4
+1 탁월한 답변, 더 많은 찬사를받을 자격이 있습니다. 특히 "최상의 사례"와 왜 좋은 (고유 한) 해시가 중요한지에 대한 이론에 대해 이야기하기 때문입니다.
Quinn Taylor

30

중요한 속성의 해시 값에 대한 간단한 XOR은 시간의 99 %이면 충분합니다.

예를 들면 다음과 같습니다.

- (NSUInteger)hash
{
    return [self.name hash] ^ [self.data hash];
}

해결책 은 Mattt Thompson ( http://nshipster.com/equality/) 에서 발견되었습니다 (이 질문도 그의 게시물에서 언급했습니다!)


1
이 답변의 문제점은 기본 값을 전혀 고려하지 않는다는 것입니다. 그리고 원시 값도 해싱에 중요 할 수 있습니다.
Vive

@Vive 이러한 문제의 대부분은 Swift에서 해결되지만 이러한 유형은 기본적이므로 일반적으로 자체 해시를 나타냅니다.
Yariv Nissim

1
Swift에 적합하지만 objc로 작성된 많은 프로젝트가 있습니다. 귀하의 답변은 objc 전용이므로 적어도 언급 할 가치가 있습니다.
Vive

해시 값을 XOR하는 것은 좋지 않은 조언이므로 많은 해시 충돌이 발생합니다. 대신 소수를 곱한 다음 다른 답변 상태로 추가하십시오.
Fishinear

27

이 스레드 는 하나의 catch로 내 메소드 isEqual:hash메소드를 구현하는 데 필요한 모든 것을 제공하는 데 매우 유용 합니다. isEqual:예제 코드 에서 객체 인스턴스 변수를 테스트 할 때 다음을 사용합니다.

if (![(id)[self name] isEqual:[aWidget name]])
    return NO;

단위 테스트에서 객체가 동일하다는 것을 알았을 때 반복적으로 실패했습니다 ( , NO 반환 ) . 그 이유는 인스턴스 변수 중 하나 가 nil 이므로 위의 설명은 다음과 같습니다.NSString

if (![nil isEqual: nil])
    return NO;

nil 은 어떤 방법에도 응답 할 것이기 때문에 이것은 완벽하게 합법적이지만

[nil isEqual: nil]

nil을 리턴합니다 . 이는 NO 이므로 테스트 할 오브젝트와 오브젝트에 nil 오브젝트가 있으면 동일하지 않은 것으로 간주됩니다 ( , NO를isEqual: 리턴 함 ).

이 간단한 수정은 if 문을 다음과 같이 변경했습니다.

if ([self name] != [aWidget name] && ![(id)[self name] isEqual:[aWidget name]])
    return NO;

그들은 둘 경우이 방법은 자신의 주소가 같은 경우는 상관없이 메서드 호출을 생략하지 전무 하거나 동일한 개체를 모두 가리키는하지만 하나가 아닌 경우 전무 하거나 다음 비교가 적절라고 다른 개체를 가리 킵니다.

이로 인해 몇 분 동안 머리를 긁지 않아도되기를 바랍니다.


20

해시 함수는 다른 객체의 해시 값과 충돌하거나 일치하지 않는 반 고유 값을 만들어야합니다.

다음은 클래스 인스턴스 변수에 적용 할 수있는 전체 해시 함수입니다. 64/32 비트 응용 프로그램의 호환성을 위해 int 대신 NSUInteger를 사용합니다.

다른 객체에 대해 결과가 0이되면 해시 충돌의 위험이 있습니다. 해시를 충돌 시키면 해시 함수에 의존하는 일부 컬렉션 클래스로 작업 할 때 예기치 않은 프로그램 동작이 발생할 수 있습니다. 사용하기 전에 해시 함수를 테스트하십시오.

-(NSUInteger)hash {
    NSUInteger result = 1;
    NSUInteger prime = 31;
    NSUInteger yesPrime = 1231;
    NSUInteger noPrime = 1237;

    // Add any object that already has a hash function (NSString)
    result = prime * result + [self.myObject hash];

    // Add primitive variables (int)
    result = prime * result + self.primitiveVariable; 

    // Boolean values (BOOL)
    result = prime * result + (self.isSelected?yesPrime:noPrime);

    return result;
}

3
여기에 하나의 문제가 있습니다 : 도트 구문을 피하기를 선호하므로 BOOL 문을 (예)로 변환했습니다 result = prime * result + [self isSelected] ? yesPrime : noPrime;. 나는 다음이 설정되었다 발견 result(예)로 1231인해, 나는 가정 ?운영자 복용 우선 순위. 대괄호를 추가하여 문제를 해결했습니다.result = prime * result + ([self isSelected] ? yesPrime : noPrime);
Ashley

12

쉽고 비효율적 인 방법은 -hash모든 인스턴스에 대해 동일한 값 을 반환하는 것 입니다. 그렇지 않으면, 균등성에 영향을 미치는 객체만을 기반으로 해시를 구현해야합니다. -isEqual:대소 문자를 구분하지 않는 문자열 비교와 같이 lax 비교를 사용하는 경우 까다로울 수 있습니다 . 정수의 경우 NSNumber와 비교하지 않는 한 일반적으로 정수 자체를 사용할 수 있습니다.

그러나 | =를 사용하지 마십시오. 포화됩니다. 대신 ^ =를 사용하십시오.

임의 재미있는 사실 : [[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]하지만 [[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]. (rdar : // 4538282, 2006 년 5 월 5 일 이후 문을 연다)


1
당신은 | =에 정확히 맞습니다. 실제로 그런 의미는 아니 었습니다. :) + =와 ^ =는 상당히 같습니다. double 및 float과 같은 정수가 아닌 프리미티브를 어떻게 처리합니까?
Dave Dribin

랜덤 재미있는 사실 : Snow Leopard에서 테스트 ... ;-)
Quinn Taylor

필드를 해시로 결합하기 위해 OR 대신 XOR을 사용하는 것이 맞습니다. 그러나 모든 객체에 대해 동일한 해시 값을 반환하라는 조언을 사용하지 마십시오. 비록 쉽지만 객체의 해시를 사용하는 모든 제품 의 성능이 크게 저하 될 수 있습니다 . 해시는하지 않습니다 동일하지 않은 객체에 대해서는 다른 것으로,하지만 당신은 그것을 달성 할 수 있다면, 그것은처럼 아무것도 없다.
Quinn Taylor

열린 레이더 버그 보고서가 닫힙니다. openradar.me/4538282 무슨 뜻인가요?
JJD

JJD, Quinn이 암시 한 것처럼 버그는 Mac OS X 10.6에서 수정되었습니다. (댓글은 2 살입니다.)
Jens Ayton

9

isEqualtrue 일 때 동일한 해시 만 제공하면됩니다 . 때 isEqual거짓, 해시 아마도 그것이 비록 불평등 필요는 없습니다. 그 후:

해시를 단순하게 유지하십시오. 가장 독특한 멤버 (또는 소수의 멤버) 변수를 선택하십시오.

예를 들어 CLPlacemark의 경우 이름만으로 충분합니다. 예, 동일한 이름을 가진 2 개 또는 3 개의 구별 CLPlacemark가 있지만 드문 경우입니다. 해시를 사용하십시오.

@interface CLPlacemark (equal)
- (BOOL)isEqual:(CLPlacemark*)other;
@end

@implementation CLPlacemark (equal)

...

-(NSUInteger) hash
{
    return self.name.hash;
}


@end

나는 도시, 국가 등을 지정하는 것을 귀찮게하지 않습니다. 이름이 충분합니다. 아마도 이름과 CLLocation 일 것입니다.

해시는 고르게 분배되어야합니다. 캐럿 ^ (xor 기호)를 사용하여 여러 멤버 변수를 결합 할 수 있습니다.

그래서 그것은 같은

hash = self.member1.hash ^ self.member2.hash ^ self.member3.hash

그렇게하면 해시가 고르게 분산됩니다.

Hash must be O(1), and not O(n)

배열에서 무엇을해야합니까?

다시 한 번 간단합니다. 배열의 모든 멤버를 해시 할 필요는 없습니다. 첫 번째 요소, 마지막 요소, 수, 중간 요소 등을 해시하기에 충분합니다.


XORing 해시 값은 균일 한 분포를 제공하지 않습니다.
Fishinear

7

이 작업을 수행하는 훨씬 쉬운 방법은 우선 - (NSString )description객체 상태의 문자열 표현을 재정의 하고 제공하는 것입니다 (이 문자열에서 객체의 전체 상태를 나타내야 함).

그런 다음 다음 구현을 제공하십시오 hash.

- (NSUInteger)hash {
    return [[self description] hash];
}

이것은 "isEqualToString : 메소드에 의해 결정된 두 문자열 오브젝트가 동일하면 동일한 해시 값을 가져야합니다"라는 원칙을 기반으로합니다.

출처 : NSString 클래스 참조


1
이는 설명 방법이 고유하다고 가정합니다. 설명의 해시를 사용하면 종속성이 생겨 명확하지 않을 수 있으며 충돌 위험이 높아집니다.
Paul Solt 2016 년

1
+1 공감 이것은 훌륭한 아이디어입니다. 설명으로 인해 충돌이 발생하는 것이 우려되는 경우이를 무시할 수 있습니다.
user4951

덕분에 짐, 나는이 해킹의 비트는 것을 부정하지 않겠지 만 내가 생각할 수있는 모든 경우에 작동합니다 - 내가 너희를 오버라이드 (override)하는 것을 제공 말했듯 description이 열등 왜, 내가 볼 수 없습니다 더 높은 투표 솔루션 중 하나. 수학적으로 가장 우아한 솔루션은 아니지만 트릭을 수행해야합니다. Brian B. (이 시점에서 가장 많이 대답 된 답변)는 다음과 같이 말합니다. "새로운 해시 알고리즘을 만들지 않으려 고 노력합니다"-동의했습니다! - 나는 단지 hashNSString!
Jonathan Ellis

깔끔한 아이디어이기 때문에 공감했습니다. 추가 NSString 할당이 두렵기 때문에 사용하지는 않겠습니다.
karwag

1
대부분의 클래스 description에는 포인터 주소가 포함되어 있기 때문에 일반적인 해결책은 아닙니다 . 따라서 이것은 동일한 해시와 동일한 동일한 클래스의 두 개의 서로 다른 인스턴스를 만듭니다. 이는 동일한 두 객체가 동일한 해시를 가지고 있다는 기본 가정을 위반합니다!
Diogo T

5

equals 및 hash 계약은 Java 세계에서 잘 지정되고 철저히 연구되었지만 (@mipardi의 답변 참조) Objective-C에도 동일한 고려 사항이 모두 적용되어야합니다.

Eclipse는 Java로 이러한 메소드를 생성하는 안정적인 작업을 수행하므로 Objective-C로 직접 이식 된 Eclipse 예제가 있습니다.

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if ([self class] != [object class])
        return false;
    MyWidget *other = (MyWidget *)object;
    if (_name == nil) {
        if (other->_name != nil)
            return false;
    }
    else if (![_name isEqual:other->_name])
        return false;
    if (_data == nil) {
        if (other->_data != nil)
            return false;
    }
    else if (![_data isEqual:other->_data])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = 1;
    result = prime * result + [_name hash];
    result = prime * result + [_data hash];
    return result;
}

그리고 YourWidget속성을 추가 하는 하위 클래스 의 경우 serialNo:

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if (![super isEqual:object])
        return false;
    if ([self class] != [object class])
        return false;
    YourWidget *other = (YourWidget *)object;
    if (_serialNo == nil) {
        if (other->_serialNo != nil)
            return false;
    }
    else if (![_serialNo isEqual:other->_serialNo])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = [super hash];
    result = prime * result + [_serialNo hash];
    return result;
}

이 구현은 isEqual:Apple 의 샘플 에서 일부 서브 클래 싱 함정을 피합니다 .

  • Apple의 클래스 테스트 other isKindOfClass:[self class]는의 두 가지 하위 클래스에 대해 비대칭입니다 MyWidget. 평등은 대칭이어야합니다. b = a 인 경우에만 a = b입니다. 테스트를로 변경하면 쉽게 해결할 수 있으며 other isKindOfClass:[MyWidget class]모든 MyWidget하위 클래스는 서로 비교할 수 있습니다.
  • isKindOfClass:서브 클래스 테스트를 사용하면 서브 클래스 isEqual:가 정제 된 동등성 테스트로 대체되지 않습니다. 이는 평등이 전이되어야하기 때문입니다. a = b이고 a = c이면 b = c입니다. MyWidget인스턴스가 두 YourWidget인스턴스와 동일하게 비교 되는 경우, 인스턴스 YourWidgetserialNo다르 더라도 해당 인스턴스가 서로 동일하게 비교되어야합니다 .

두 번째 문제는 객체가 정확히 동일한 클래스에 속하는 경우에만 객체를 동일하게 간주하여 해결할 수 있으므로 [self class] != [object class]여기서 테스트하십시오. 일반적인 응용 프로그램 클래스의 경우 이것이 최선의 방법 인 것 같습니다.

그러나 isKindOfClass:테스트가 바람직한 경우 가 있습니다. 이것은 어플리케이션 클래스보다 일반적인 프레임 워크 클래스입니다. 예를 들어, / 구분 및 클래스 클러스터의 개인 클래스와 관계없이 동일한 기본 문자 순서를 가진 NSString다른 NSString문자와 비교해야합니다 .NSStringNSMutableStringNSString

이러한 경우 isEqual:잘 정의되고 문서화되어있는 동작이 있어야하며 서브 클래스가이를 재정의 할 수 없음을 분명히해야합니다. Java에서는 equals 및 hashcode 메소드를로 플래그 지정하여 '재정의 없음'제한을 적용 할 수 final있지만 Objective-C에는 해당 사항이 없습니다.


@adubr 마지막 ​​두 단락에서 다룹니다. MyWidget클래스 클러스터가 아닌 것으로 이해되는 것은 초점 이 아닙니다.
jedwidz

5

이것은 귀하의 질문에 직접 대답하지는 않지만 해머를 생성하기 전에 MurmurHash를 사용했습니다 : murmurhash

내가 이유를 설명해야 할 것 같아 : murmurhash는 피가 빠르다 ...


2
난수를 사용하여 void * 키의 고유 해시에 초점을 맞춘 C ++ 라이브러리 (Objective-C 객체와 관련이 없음)는 실제로 유용한 제안이 아닙니다. -hash 메소드는 매번 일관된 값을 반환해야합니다. 그렇지 않으면 전혀 쓸모가 없습니다. 오브젝트가 -hash를 호출하는 콜렉션에 추가되고 매번 새 값을 리턴하는 경우 중복은 감지되지 않으며 콜렉션에서 오브젝트를 검색 할 수 없습니다. 이 경우 "해시"라는 용어는 보안 / 암호화의 의미와 다릅니다.
Quinn Taylor

3
murmurhash는 암호화 해시 기능이 아닙니다. 잘못된 정보를 게시하기 전에 사실을 확인하십시오. Murmurhash 사용자 정의 objective-c 클래스를 해시하는 데 유용 할 수 있습니다 (특히 NSData가 많은 경우 특히 빠름). 그러나 누군가에게 "단지 objective-c를 집어 올리는"것이 최선의 조언이 아니라고 제안 할 수도 있지만 질문에 대한 원래의 답장에 접두사를 적어 두십시오.
schwa


4

나는 Objective C 초보자이기도하지만 Objective C의 정체성과 평등에 관한 훌륭한 기사를 여기 에서 찾았 습니다 . 내 독서에서 기본 해시 함수 (고유 한 ID를 제공해야 함)를 유지하고 isEqual 메소드를 구현하여 데이터 값을 비교할 수있는 것처럼 보입니다.


나는 Cocoa / Objective C 초보자이며,이 답변과 링크는 위의 모든 고급 항목을 최종 결론으로 ​​줄이는 데 실제로 도움이되었습니다. 해시에 대해 걱정할 필요가 없습니다-단지 isEqual : 메소드를 구현하십시오. 감사!
John Gallagher

@ceperry의 링크를 놓치지 마세요. Equality vs IdentityKarl Kraft 의 기사 는 정말 좋습니다.
JJD

6
@ 존 : 나는 당신이 기사를 다시 읽어야한다고 생각합니다. "같은 인스턴스는 동일한 해시 값을 가져야합니다." 재정의하면 재정의 isEqual:해야합니다 hash.
Steve Madsen

3

Quinn은 murmur hash에 대한 참조가 여기서 쓸모가 없다는 것이 잘못되었습니다. Quinn은 해싱의 이론을 이해하고 싶은 것이 옳습니다. 이 불평은 그 이론의 많은 부분을 구현으로 증류시킨다. 이 구현을이 특정 응용 프로그램에 적용하는 방법을 알아내는 것이 좋습니다.

주요 요점은 다음과 같습니다.

tcurdt의 예제 함수는 '31'이 소수이기 때문에 좋은 승수임을 나타냅니다. 소수가되는 것이 필요하고 충분한 조건임을 보여줄 필요가있다. 실제로 31 (및 7)은 31 == -1 % 32이므로 특히 좋은 소수가 아닐 수 있습니다. 비트 세트가 약 절반이고 비트가 반이되는 홀수 승수가 더 좋을 수 있습니다. (murmur 해시 곱셈 상수에는 해당 속성이 있습니다.)

이 유형의 해시 함수는 곱한 후 결과 값이 shift와 xor를 통해 조정되면 더 강력 해집니다. 곱셈은 ​​레지스터의 하이 엔드에서 많은 비트 인터랙션의 결과를 생성하고 레지스터의 맨 아래 엔드에서 낮은 인터랙션 결과를 생성하는 경향이 있습니다. shift와 xor는 레지스터의 하단에서 상호 작용을 증가시킵니다.

초기 결과를 비트의 절반이 0이고 비트의 절반이 1 인 값으로 설정하는 것도 유용합니다.

요소가 결합되는 순서에주의하는 것이 유용 할 수 있습니다. 먼저 부울 및 값이 강력하게 분산되지 않은 다른 요소를 먼저 처리해야합니다.

계산이 끝날 때 두 개의 여분의 비트 스크램블링 단계를 추가하는 것이 유용 할 수 있습니다.

이 응용 프로그램에 대해 murmur 해시가 실제로 빠른지 여부는 공개 질문입니다. murmur 해시는 각 입력 단어의 비트를 사전 혼합합니다. 여러 입력 단어를 병렬로 처리하여 여러 발행 파이프 라인 CPU에 도움이됩니다.


3

@tcurdt의 답변과 @ oscar-gomez의 속성 이름얻는 답변을 결합하면 isEqual 및 해시 모두에 대한 쉬운 드롭 인 솔루션을 만들 수 있습니다.

NSArray *PropertyNamesFromObject(id object)
{
    unsigned int propertyCount = 0;
    objc_property_t * properties = class_copyPropertyList([object class], &propertyCount);
    NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity:propertyCount];

    for (unsigned int i = 0; i < propertyCount; ++i) {
        objc_property_t property = properties[i];
        const char * name = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:name];
        [propertyNames addObject:propertyName];
    }
    free(properties);
    return propertyNames;
}

BOOL IsEqualObjects(id object1, id object2)
{
    if (object1 == object2)
        return YES;
    if (!object1 || ![object2 isKindOfClass:[object1 class]])
        return NO;

    NSArray *propertyNames = PropertyNamesFromObject(object1);
    for (NSString *propertyName in propertyNames) {
        if (([object1 valueForKey:propertyName] != [object2 valueForKey:propertyName])
            && (![[object1 valueForKey:propertyName] isEqual:[object2 valueForKey:propertyName]])) return NO;
    }

    return YES;
}

NSUInteger MagicHash(id object)
{
    NSUInteger prime = 31;
    NSUInteger result = 1;

    NSArray *propertyNames = PropertyNamesFromObject(object);

    for (NSString *propertyName in propertyNames) {
        id value = [object valueForKey:propertyName];
        result = prime * result + [value hash];
    }

    return result;
}

이제 커스텀 클래스에서 쉽게 구현 isEqual:하고 hash다음을 수행 할 수 있습니다 .

- (NSUInteger)hash
{
    return MagicHash(self);
}

- (BOOL)isEqual:(id)other
{
    return IsEqualObjects(self, other);
}

2

생성 후 변경 될 수있는 객체를 생성하는 경우 객체가 컬렉션에 삽입 된 경우 해시 값이 변경되지 않아야합니다 . 실제로 이것은 초기 객체 생성 시점에서 해시 값을 수정해야 함을 의미합니다. 자세한 정보 는 NSObject 프로토콜의 -hash 메소드에 대한 Apple의 문서 를 참조하십시오.

컬렉션에서 개체의 위치를 ​​결정하기 위해 해시 값을 사용하는 컬렉션에 변경 가능한 개체가 추가되면 개체의 컬렉션에있는 동안 개체의 해시 메서드가 반환하는 값이 변경되지 않아야합니다. 따라서 해시 방법은 객체의 내부 상태 정보에 의존해서는 안되며 객체가 컬렉션에있는 동안 객체의 내부 상태 정보가 변경되지 않아야합니다. 예를 들어, 변경 가능한 사전을 해시 테이블에 넣을 수 있지만 그 안에있는 동안이를 변경해서는 안됩니다. (주어진 객체가 컬렉션에 있는지 여부를 알기가 어려울 수 있습니다.)

이것은 잠재적으로 해시 조회를 훨씬 덜 효율적으로 렌더링하기 때문에 완전한 해커처럼 들리지만주의 측면에서 실수를하고 문서의 내용을 따르는 것이 좋습니다.


1
해시 문서를 잘못 읽고 있습니다. 본질적으로 "어느 쪽이든"상황입니다. 객체가 변경되면 일반적으로 해시도 변경됩니다. 이것은 프로그래머에게 경고입니다. 개체를 변경 한 결과 해시가 변경되면 해시를 사용하는 컬렉션에있는 동안 개체를 변경하면 예기치 않은 동작이 발생합니다. 이러한 상황에서 객체를 "안전하게 변경 가능"해야하는 경우 해시를 변경 가능 상태와 관련이 없도록 선택할 수 없습니다. 그 특별한 상황은 나에게 이상하게 들리지만, 그것이 적용되는 경우는 드물게 있습니다.
Quinn Taylor

1

내가 완전한 boffin을 여기에서 들릴 위험이 있지만 ... ... 아무도 '모범 사례'를 따르기 위해 귀찮게 언급하지 않았다면 대상 객체가 소유 한 모든 데이터를 고려하지 않는 equals 메소드를 지정해서는 안됩니다. 동등성을 구현할 때는 데이터가 개체에 집계되고 그와 관련된 개체를 고려해야합니다. 비교에서 '연령'을 고려하지 않으려면 비교기를 작성하고 isEqual : 대신 비교를 수행하는 데 사용해야합니다.

등식 비교를 임의로 수행하는 isEqual : 메소드를 정의하는 경우 등식 해석에서 '트위스트'를 잊어 버린 경우 다른 개발자 또는 자신이이 메소드를 잘못 사용할 위험이 있습니다.

Ergo는 해싱에 대한 훌륭한 질문과 대답이지만 일반적으로 해싱 방법을 재정의 할 필요는 없으며 대신 임시 비교기를 정의해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.