Найкраща практика переосмислення isEqual: і хеш


267

Як ви правильно перекриваєте isEqual:в Objective-C? «Улов» здається таким, що якщо два об’єкти рівні (як визначено isEqual:методом), вони повинні мати однакове хеш-значення.

У розділі інтроспекції Посібника з основ какао є приклад того, як перекрити 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:, що тільки перевіряє nameі dataвластивості. Приклад не показує, як перекрити hash.

Припустимо, є й інші властивості, які не впливають на рівність, скажімо age. Чи не варто hashметод бути перевизначений таким чином, що тільки nameі dataвпливають на хеш? І якщо так, то як би ви це зробили? Просто додайте хеші nameта data? Наприклад:

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

Це достатньо? Чи є краща техніка? Що робити, якщо у вас є примітиви, як int? Перетворити їх, щоб NSNumberотримати хеш? Або конструкції як NSRect?

( Мозговий пердець : Спочатку написав "побіжно АБО" їх ​​разом із |=. Має на увазі.)


2
if (![other isKindOfClass:[self class]])- Це технічно означає, що рівність не буде комунальною. Тобто A = B не означає B = A (наприклад, якщо один є підкласом іншого)
Роберт

Посилання на документацію мертве, тепер архівоване до Introspection
jedwidz

Відповіді:


111

Починати з

 NSUInteger prime = 31;
 NSUInteger result = 1;

Тоді для кожного примітиву, який ти робиш

 result = prime * result + var

Для об'єктів ви використовуєте 0 для нуля, а в іншому випадку - їх хеш-код.

 result = prime * result + [var hash];

Для булевих ви використовуєте два різних значення

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

Пояснення та атрибуція

Це не робота Tcurdt, і коментарі просили отримати більше пояснень, тому я вважаю, що редакція щодо віднесення прав справедлива.

Цей алгоритм був популяризований у книзі "Ефективна Java", і відповідну главу зараз можна знайти тут . Ця книга популяризувала алгоритм, який тепер є типовим для багатьох програм Java (включаючи Eclipse). Однак це походить від ще більш старого втілення, яке по-різному приписується Дану Бернштейну або Крісу Тореку. Цей старший алгоритм спочатку плавав навколо Usenet, і певна атрибуція складна. Наприклад, у цьому коді Apache (пошук їх імен) є цікавий коментар, який посилається на першоджерело.

Підсумок полягає в тому, що це дуже старий простий алгоритм хешування. Він не найефективніший, і математично навіть не доведено, що це «хороший» алгоритм. Але це просто, і багато людей використовували його давно з хорошими результатами, тому він має велику історичну підтримку.


9
Звідки взявся 1231: 1237? Я бачу це і в Java Boolean.hashCode (). Це магічно?
Девід Леонард

17
Природа алгоритмів хешування - це зіткнення. Тож я не бачу вашої точки зору, Пол.
tcurdt

85
На мою думку, ця відповідь не відповідає на власне запитання (найкращі практики для перегляду хешу NSObject). Він просто забезпечує один конкретний алгоритм хешу. Крім того, розрізненість пояснень ускладнює розуміння без глибоких знань з цього питання, і може призвести до того, що люди використовують його, не знаючи, що вони роблять. Я не розумію, чому це питання має стільки відгуків.
Рікардо Санчес-Саес

6
Перша проблема - (int) невелика і легко переповнюється, використовуйте NSUInteger. 2-я проблема - Якщо ви продовжуєте множувати результат на кожну хеш змінної, ваш результат переповниться. напр. [HSString хеш] створює великі значення. Якщо у вас є 5+ змінних, легко переповнити цей алгоритм. Це призведе до того, що все відображення в той же хеш, що погано. Дивіться мою відповідь: stackoverflow.com/a/4393493/276626
Пол Солт

10
@PaulSolt - Переповнення не є проблемою у створенні хешу, зіткнення є. Але переповнення не обов'язково робить зіткнення більше ймовірним, і ваше твердження про переповнення, яке спричиняє все, щоб зіставити один і той же хеш, просто неправильне.
DougW

81

Я просто підбираю Objective-C, тому я не можу говорити спеціально для цієї мови, але іншими мовами, якими я користуюся, якщо два екземпляри є "рівними", вони повинні повернути один і той же хеш - інакше у вас буде все різноманітні проблеми при спробі використовувати їх як ключі в хештеле (або будь-яких колекціях словника).

З іншого боку, якщо два екземпляри не рівні, вони можуть мати або не мати однаковий хеш - найкраще, якщо їх немає. Це різниця між пошуком O (1) на хеш-таблиці та O (N) пошуку - якщо всі ваші хеші зіткнуться, ви можете виявити, що пошук вашої таблиці не є кращим, ніж пошук у списку.

З точки зору кращих практик, ваш хеш повинен повернути випадковий розподіл значень для його введення. Це означає, що, наприклад, якщо у вас є подвійний, але більшість ваших значень мають тенденцію до кластеризації від 0 до 100, вам потрібно переконатися, що хеші, повернені цими значеннями, рівномірно розподіляються по всьому діапазону можливих хеш-значень . Це значно покращить вашу ефективність.

Існує декілька алгоритмів хешування, зокрема кілька перерахованих тут. Я намагаюся уникати створення нових хеш-алгоритмів, оскільки це може мати великі наслідки для продуктивності, тому використання існуючих методів хешу та здійснення побітової комбінації якогось типу у вашому прикладі - це хороший спосіб уникнути цього.


4
+1 Відмінна відповідь, заслуговує на більшу кількість результатів, тим більше, що він насправді говорить про "кращі практики" та теорію, чому важливий хороший (унікальний) хеш.
Квінн Тейлор

30

Простий XOR над хеш-значеннями критичних властивостей займає 99% часу.

Наприклад:

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

Рішення знайдено на веб-сайті http://nshipster.com/equality/ Меттом Томпсоном (який також передав це питання у своєму дописі!)


1
Проблема цієї відповіді полягає в тому, що вона взагалі не враховує примітивні значення. І примітивні значення також можуть бути важливими для хешування.
Vive

@Vive Більшість цих проблем вирішуються у Swift, але ці типи зазвичай представляють власний хеш, оскільки вони примітивні.
Ярів Ніссім

1
Хоча ви маєте право на Swift, все ж є багато проектів, написаних за допомогою objc. Оскільки ваша відповідь присвячена objc, варто принаймні згадати.
Vive

XORing значення хешу разом - це погана порада, це призводить до багатьох хеш-колізій. Натомість помножте на просте число та додайте, як свідчать інші відповіді.
fishinear

27

Я вважав цю тему надзвичайно корисною, надаючи все, що потрібно для моєї роботи isEqual:та hashметодів, що реалізуються одним уловом. При тестуванні змінних екземплярів об'єкта в isEqual:прикладі коду використовується:

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

Це неодноразово виходило з ладу ( тобто поверталося НІ ) без помилок, коли я знав, що об'єкти однакові в моєму тестуванні одиниць. Причина полягала в тому, що одна зі NSStringзмінних екземплярів була нульовою, тому наведене вище твердження було:

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

і оскільки нуль відповість на будь-який метод, це абсолютно законно, але

[nil isEqual: nil]

повертає NIL , який є NO , тому , коли обидва об'єкти і один випробовуються були нуль об'єкта вони будуть розглядатися не рівні ( тобто , isEqual:буде повертати NO ).

Це просте виправлення було змінити оператор if на:

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

Таким чином, якщо їхні адреси однакові, пропускає виклик методу незалежно від того, обидва вони нульові або обидва вказують на один і той же об'єкт, але якщо або не є нульовим, або вони вказують на різні об'єкти, тоді компаратор викликається відповідним чином.

Я сподіваюся, що це заощадить комусь кілька хвилин чесання голови.


20

Хеш-функція повинна створювати напів унікальне значення, яке, швидше за все, не зіткнеться або не відповідає хеш-значенню іншого об’єкта.

Ось повна хеш-функція, яку можна адаптувати до змінних ваших примірників класів. Він використовує замість int для сумісності 64/32-бітних додатків 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);
Ешлі,

12

Простий, але неефективний спосіб - повернути однакове -hashзначення для кожного екземпляра. В іншому випадку, так, ви повинні реалізовувати хеш лише на об'єктах, які впливають на рівність. Це складно, якщо ви використовуєте розслаблені порівняння -isEqual:(наприклад, порівняння рядків з нечутливими до регістру). Для ints, ви можете використовувати сам int, якщо ви не порівнюєте з NSNumbers.

Не використовуйте | =, однак це наситить. Використовуйте замість ^ =.

Випадковий цікавий факт:, [[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]але [[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]. (rdar: // 4538282, відкрито з 05 травня 2006 р.)


1
Ви абсолютно праві на | =. Це насправді не означало цього. :) + = і ^ = досить рівнозначні. Як ви обробляєте нецілі примитиви, такі як double та float?
Дейв Дрибін

Випадковий цікавий факт: випробуй його на Snow Leopard ... ;-)
Квінн Тейлор

Він правильно використовує XOR замість АБО для комбінування полів у хеш. Однак не використовуйте поради щодо повернення однакового значення -hash для кожного об'єкта - хоча це легко, він може сильно погіршити продуктивність всього, що використовує хеш об'єкта. Хеш не повинен відрізнятись від об'єктів, які не рівні, але якщо ви зможете цього досягти, нічого подібного немає.
Квінн Тейлор

Звіт про помилку про відкриту радіолокацію закрито. openradar.me/4538282 Що це означає?
JJD

JJD, помилка була виправлена ​​в Mac OS X 10.6, як натякнув Куїнн. (Зауважте, що коментарю два роки.)
Йенс Айтон

9

Пам'ятайте, що вам потрібно вказати хеш, рівний тоді, коли isEqualце правда. Коли isEqualfalse, хеш не повинен бути неоднаковим, хоча, мабуть, він є. Звідси:

Зберігати хеш просто. Виберіть змінну члена (або декількох членів), яка є найбільш характерною.

Наприклад, для CLPlacemark достатньо лише імені. Так, існує 2 або 3 різних CLPlacemark з точно такою ж назвою, але вони рідкісні. Використовуйте цей хеш.

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

@implementation CLPlacemark (equal)

...

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


@end

Зауважте, я не турбуюся, вказуючи місто, країну тощо. Назви достатньо. Можливо, ім'я та CLLocation.

Хеш слід розподіляти рівномірно. Таким чином, ви можете комбінувати змінну декількох членів, використовуючи знак caret ^ (знак xor)

Так це щось на кшталт

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

Таким чином хеш буде розподілений рівномірно.

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

То що робити в масиві?

Знову ж таки просто. Вам не доведеться хешувати всіх членів масиву. Досить хешувати перший елемент, останній елемент, кількість, можливо, деякі середні елементи, і все.


Хеш-значення XORing не дає рівномірного розподілу.
риболовля

7

Тримайтеся, безумовно, набагато простіший спосіб зробити це спочатку переосмислити - (NSString )descriptionі надати рядкове представлення стану вашого об'єкта (ви повинні представити весь стан свого об'єкта в цій рядку).

Потім просто надайте таку реалізацію hash:

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

Це ґрунтується на принципі, що "якщо два об'єкти рядка рівні (як визначено методом isEqualToString:), вони повинні мати однакове хеш-значення".

Джерело: Довідка про клас NSString


1
Це передбачає, що метод опису буде унікальним. Використання хеша опису створює залежність, яка може бути не очевидною, і підвищує ризик зіткнень.
Пол Солт

1
+1 Оголошено. Це приголомшлива ідея. Якщо ви боїтесь, що описи спричиняють зіткнення, тоді ви можете їх перекрити.
user4951

Дякую Джиме, я не заперечу, що це трохи хакер, але це спрацює у будь-якому випадку, про що я можу подумати - і, як я вже сказав, за умови, що ти перекриєш description, я не розумію, чому це поступається будь-яке рішення, яке проголосувало вище. Це може бути не самим математично витонченим рішенням, але варто зробити трюк. Як зазначає Брайан Б. (найбільш відповідна відповідь на даний момент): "Я намагаюся уникати створення нових хеш-алгоритмів" - погодився! - Я просто ! hashNSString
Джонатан Елліс

Оновлено, тому що це охайна ідея. Я не збираюся його використовувати, хоча боюсь додаткових виділень NSString.
karwag

1
Це не є загальним рішенням, оскільки для більшості класів descriptionвключає адресу вказівника. Отже, це робить два різних екземпляри одного класу, які рівні з різними хешами, які порушують основне припущення про те, що два рівні об’єкти мають однаковий хеш!
Diogo T

5

Договори рівності та хеш-книг добре визначені та ретельно досліджені в світі Java (див. Відповідь @ mipardi), але всі ті ж міркування повинні стосуватися і Objective-C.

Eclipse виконує надійну роботу з генерування цих методів на Java, тому ось приклад Eclipse, перенесений вручну в Objective-C:

- (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. Рівність повинна бути симетричною: a = b, якщо і лише тоді, коли b = a. Це можна легко виправити, змінивши тест наother isKindOfClass:[MyWidget class] , тоді всі MyWidgetпідкласи були б взаємно порівнянні.
  • Використання isKindOfClass:тесту підкласу не дає змоги переосмислити підкласи isEqual:з уточненим тестом рівності. Це тому, що рівність повинна бути транзитивною: якщо a = b і a = c, то b = c. Якщо MyWidgetекземпляр порівнює два YourWidgetекземпляри, то ці YourWidgetекземпляри повинні порівнювати один одного, навіть якщо вони serialNoвідрізняються.

Друге питання можна виправити, лише вважаючи об'єкти рівними, якщо вони належать до того ж класу, звідси і [self class] != [object class]тест тут. Для типових класів прикладних програм це здається найкращим підходом.

Однак, безумовно, є випадки, коли isKindOfClass:тест є кращим. Це більше характерно для базових класів, ніж для класів додатків. Наприклад, будь-який NSStringповинен порівнювати рівний з будь-яким іншим NSStringз тією ж послідовністю базових символів, незалежно від NSString/ NSMutableStringрозрізнення, а також незалежно від того, які приватні класи NSStringкластерного класу беруть участь.

У таких випадках isEqual:має бути чітко визначена, добре задокументована поведінка, і повинно бути зрозуміло, що підкласи не можуть це змінити. У Java обмеження "без переопрацювання" може бути застосоване, позначивши методи рівності та хеш-коду як final, але у Objective-C немає еквівалента.


@adubr Це висвітлено в моїх останніх двох абзацах. Це не фокусно, як MyWidgetрозуміється, це не кластерний клас.
jedwidz

5

Це зовсім не відповідає на ваше запитання (зовсім), але я раніше використовував MurmurHash, щоб генерувати хеші: murmurhash

Зрозуміло, я мушу пояснити, чому: шум швидко криється ...


2
Бібліотека C ++, яка зосереджена на унікальних хешах для пустоти * ключа з використанням випадкового числа (а також не стосується об’єктів Objective-C), насправді тут не є корисною пропозицією. Метод -hash повинен кожного разу повертати послідовне значення, або він буде абсолютно марним. Якщо об’єкт додається до колекції, яка викликає -hash, і щоразу повертає нове значення, дублікати ніколи не будуть виявлені, і ви також не зможете отримати об'єкт із колекції. У цьому випадку термін "хеш" відрізняється від значення в безпеці / криптографії.
Квінн Тейлор

3
murmurhash - це не криптографічна хеш-функція. Будь ласка, перевірте свої факти, перш ніж публікувати невірну інформацію. Murmurhash може бути корисним для хеш- налаштування спеціальних класів target -c (особливо, якщо у вас задіяно багато NSDatas), оскільки це надзвичайно швидко. Однак я надаю вам те, що, мабуть, підказує, що це не найкраща порада, щоб дати комусь "просто збирання Objective-c", але, будь ласка, відзначте мій приставку в моїй оригінальній відповіді на питання.
schwa

5

Я виявив, що ця сторінка є корисним посібником щодо методів вирівнювання рівних та хеш-типів. Він включає гідний алгоритм для обчислення хеш-кодів. Сторінка орієнтована на Java, але досить легко її адаптувати до Objective-C / Cocoa.


1
кешоване посилання через archive.org: web.archive.org/web/20071013053633/http://www.geocities.com/…
cobbal

4

Я Об'єктивний C новачок, але я знайшов відмінну статтю про особистості проти рівності в Objective C тут . З мого читання, схоже, ви можете просто зберегти хеш-функцію за замовчуванням (яка повинна забезпечувати унікальну ідентичність) та застосувати метод isEqual, щоб він порівнював значення даних.


Я новачок з какао / об'єктивом C, і ця відповідь та посилання справді допомогли мені прорізати всі більш досконалі речі вище до нижнього рядка - мені не потрібно турбуватися про хеши - просто впроваджуючи метод isEqual:. Дякую!
Джон Галлахер

Не пропустіть посилання @ ceperry. Стаття Equality vs IdentityКарла Крафта справді гарна.
JJD

6
@John: Я думаю, вам слід перечитати статтю. Це дуже чітко говорить про те, що "рівні екземпляри повинні мати однакові хеш-значення". Якщо ви перекриєте isEqual:, ви також повинні перекрити hash.
Стів Мадсен

3

Квін просто помиляється, що посилання на хеш-шум тут марний. Квін прав, що ви хочете зрозуміти теорію хешування. Шум перетворює багато цієї теорії на реалізацію. З'ясувати, як застосувати цю реалізацію до цього конкретного додатка, варто вивчити.

Деякі ключові моменти тут:

Прикладна функція від tcurdt дозволяє припустити, що «31» - це хороший множник, оскільки він є простим. Потрібно показати, що бути прем'єр - це необхідна і достатня умова. Насправді 31 (і 7), ймовірно, не є дуже хорошими прайменами, тому що 31 == -1% 32. Непарний множник з приблизно половиною встановлених бітів і половиною бітів ясний, ймовірно, буде кращим. (Константа множення хеш-шуму має таку властивість.)

Цей тип хеш-функції, ймовірно, буде сильнішим, якби після множення значення результату було скориговано за допомогою зсуву та xor. Множення має тенденцію до отримання результатів безлічі бітових взаємодій у високому кінці регістра та низьких результатів взаємодії в нижньому кінці регістра. Зсув і xor збільшують взаємодії в нижньому кінці регістра.

Визначення початкового результату значенням, де приблизно половина бітів дорівнює нулю, і приблизно половина бітів - це також, як правило, буде корисним.

Може бути корисним бути уважним щодо порядку, в якому поєднуються елементи. Напевно, слід спочатку обробити булеві та інші елементи, де значення не сильно розподілені.

Може бути корисним додати пару додаткових етапів скремтування в кінці обчислення.

Незалежно від того, чи справді хеш-шум дійсно швидкий для цього додатка, питання є відкритим. Хеш-шум попередньо змішує біти кожного введеного слова. Паралельно можна обробляти кілька вхідних слів, що допомагає багатопрофільному конвеєру.


3

Поєднуючи відповідь @ tcurdt з відповіддю @ oscar-gomez для отримання імен властивостей , ми можемо створити просте рішення для випуску як для рівних, так і для хеш-програм:

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

Зауважте, що якщо ви створюєте об'єкт, який після створення може бути вимкнено, значення хеша не повинно змінюватися, якщо об’єкт вставлений у колекцію. Практично кажучи, це означає, що значення хеша має бути зафіксовано з точки початкового створення об'єкта. Додаткову інформацію див. У документації Apple щодо методу -hash протоколу NSObject :

Якщо об'єкт, що змінюється, додається до колекції, яка використовує хеш-значення для визначення положення об'єкта в колекції, значення, повернене хеш-методом об'єкта, не повинно змінюватися, поки об’єкт знаходиться в колекції. Отже, або метод хешу не повинен покладатися на будь-яку інформацію про внутрішній стан об'єкта, або ви повинні переконатися, що інформація про внутрішній стан об'єкта не змінюється, поки об'єкт знаходиться в колекції. Так, наприклад, змінений словник можна помістити в хеш-таблицю, але ви не повинні змінювати його, поки він знаходиться там. (Зауважте, що може бути важко дізнатися, чи є даний колекція у колекції чи ні.)

Мені це здається повним викраденням, оскільки воно потенційно ефективно робить пошук хешу набагато менш ефективним, але я вважаю, що краще помилитися з боку обережності та слідувати тому, що йдеться в документації.


1
Ви неправильно читаєте хеш-документи - це, по суті, ситуація "або-або". Якщо об'єкт змінюється, хеш зазвичай змінюється. Це дійсно попередження програміста, що якщо хеш зміниться в результаті мутації об'єкта, то зміна об'єкта, поки він знаходиться в колекції, що використовує хеш, спричинить несподівану поведінку. Якщо об'єкт повинен бути "безпечно змінений" у такій ситуації, у вас немає іншого вибору, крім того, щоб зробити хеш не пов’язаним із станом, що змінюється. Ця конкретна ситуація здається мені дивною, але, безумовно, є нечасті ситуації, коли це стосується.
Квінн Тейлор

1

Вибачте, якщо я ризикую озвучити повну скриньку тут, але ... ... ніхто не заважав згадувати про те, щоб дотримуватися "кращих практик", ви точно не повинні вказувати метод рівності, який НЕ враховував би всі дані, що належать вашому цільовому об'єкту, наприклад, що б там не було дані агрегуються до вашого об’єкта, порівняно з його асоційованими особами, повинні враховуватися при реалізації рівних. Якщо ви не хочете брати до уваги, скажімо, "вік" враховуйте для порівняння, тоді вам слід написати компаратор і використовувати його для порівняння замість isEqual:.

Якщо ви визначите метод isEqual:, який виконує порівняння рівності довільно, ви несете ризик того, що цим методом неправомірно користується інший розробник, або навіть ви самі, після того, як ви забули "поворот" у своїй інтервальній інтерпретації.

Ерго, хоча це велике питання щодо хешування, вам зазвичай не потрібно переосмислювати метод хешування, ви, мабуть, повинні визначити спеціальний порівняльник замість цього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.