Las mejores prácticas para anular isEqual: y hash

267

¿Cómo anula correctamente isEqual:en Objective-C? La "captura" parece ser que si dos objetos son iguales (según lo determinado por el isEqual:método), deben tener el mismo valor hash.

La sección de Introspección de la Guía de Fundamentos de Cocoa tiene un ejemplo sobre cómo anular isEqual:, copiado de la siguiente manera, para una clase llamada 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;
}

Comprueba la igualdad del puntero, luego la igualdad de clase, y finalmente compara los objetos usando isEqualToWidget:, que solo comprueba las propiedades namey data. Lo que el ejemplo no muestra es cómo anular hash.

Supongamos que hay otras propiedades que no afectan la igualdad, por ejemplo age. No debería el hashmétodo puede anular tal que sólo namey dataafecta el hash? Y si es así, ¿cómo harías eso? Simplemente agregue los hashes de namey data? Por ejemplo:

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

¿Es eso suficiente? ¿Hay una mejor técnica? ¿Qué pasa si tienes primitivos, como int? Convertirlos NSNumberpara obtener su hash? O estructuras como NSRect?

( Pedo cerebral : Originalmente escribí "bitwise O" junto con ellos |=. Significaba agregar).

Dave Dribin
fuente
2
if (![other isKindOfClass:[self class]])- Esto técnicamente significa que la igualdad no será conmutativa. Es decir, A = B no significa B = A (por ejemplo, si una es una subclase de la otra)
Robert
El enlace de documentación está muerto, ahora archivado en Introspección
jedwidz

Respuestas:

111

Empezar con

 NSUInteger prime = 31;
 NSUInteger result = 1;

Entonces por cada primitivo que hagas

 result = prime * result + var

Para los objetos, usa 0 para nulo y, de lo contrario, su código hash.

 result = prime * result + [var hash];

Para booleanos usas dos valores diferentes

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

Explicación y Atribución

Este no es el trabajo de tcurdt, y los comentarios pedían más explicaciones, por lo que creo que una edición para la atribución es justa.

Este algoritmo se popularizó en el libro "Java efectivo", y el capítulo correspondiente se puede encontrar en línea aquí . Ese libro popularizó el algoritmo, que ahora es un valor predeterminado en una serie de aplicaciones Java (incluido Eclipse). Sin embargo, se derivó de una implementación aún más antigua que se atribuye de diversas maneras a Dan Bernstein o Chris Torek. Ese antiguo algoritmo originalmente flotaba en Usenet, y cierta atribución es difícil. Por ejemplo, hay algunos comentarios interesantes en este código de Apache (busque sus nombres) que haga referencia a la fuente original.

En pocas palabras, este es un algoritmo de hashing simple muy antiguo. No es el más eficiente, y ni siquiera se ha demostrado matemáticamente que sea un "buen" algoritmo. Pero es simple, y mucha gente lo ha usado durante mucho tiempo con buenos resultados, por lo que tiene mucho apoyo histórico.

tcurdt
fuente
9
¿De dónde vino el 1231: 1237? Lo veo en Boolean.hashCode () de Java también. ¿Es mágico?
David Leonard
17
Es la naturaleza de los algoritmos de hashing que habrá colisiones. Así que no entiendo tu punto, Paul.
tcurdt
85
En mi opinión, esta respuesta no responde a la pregunta real (mejores prácticas para anular el hash de NSObject). Solo proporciona un algoritmo hash particular. Además de eso, la escasez de la explicación hace que sea difícil de entender sin un conocimiento profundo sobre el tema, y ​​puede hacer que las personas lo usen sin saber qué están haciendo. No entiendo por qué esta pregunta tiene tantos votos positivos.
Ricardo Sanchez-Saez
66
Primer problema: (int) es pequeño y fácil de desbordar, use NSUInteger. Segundo problema: si sigue multiplicando el resultado por cada hash variable, su resultado se desbordará. p.ej. [NSString hash] crea valores grandes. Si tiene más de 5 variables, es fácil desbordar con este algoritmo. Esto dará como resultado que todo se asigne al mismo hash, lo cual es malo. Vea mi respuesta: stackoverflow.com/a/4393493/276626
Paul Solt
10
@PaulSolt: el desbordamiento no es un problema para generar un hash, la colisión sí lo es. Pero el desbordamiento no necesariamente hace que la colisión sea más probable, y su declaración sobre el desbordamiento que hace que todo se asigne al mismo hash es simplemente incorrecta.
DougW
81

Solo estoy recogiendo Objective-C, así que no puedo hablar específicamente para ese idioma, pero en los otros idiomas que uso si dos instancias son "Iguales", deben devolver el mismo hash; de lo contrario, tendrá todo problemas al intentar usarlos como claves en una tabla hash (o cualquier colección de tipo diccionario).

Por otro lado, si 2 instancias no son iguales, pueden o no tener el mismo hash; es mejor si no lo tienen. Esta es la diferencia entre una búsqueda O (1) en una tabla hash y una búsqueda O (N): si todos sus hash chocan, puede encontrar que buscar en su tabla no es mejor que buscar en una lista.

En términos de mejores prácticas, su hash debería devolver una distribución aleatoria de valores para su entrada. Esto significa que, por ejemplo, si tiene un doble, pero la mayoría de sus valores tienden a agruparse entre 0 y 100, debe asegurarse de que los hashes devueltos por esos valores se distribuyan uniformemente en todo el rango de posibles valores hash . Esto mejorará significativamente su rendimiento.

Existen varios algoritmos de hash, incluidos varios enumerados aquí. Intento evitar crear nuevos algoritmos hash, ya que puede tener grandes implicaciones de rendimiento, por lo que usar los métodos hash existentes y hacer una combinación de algún tipo de bit a bit como lo hace en su ejemplo es una buena manera de evitarlo.

Brian B.
fuente
44
+1 Excelente respuesta, merece más votos a favor, especialmente porque en realidad habla de "mejores prácticas" y la teoría detrás de por qué es importante un buen hash (único).
Quinn Taylor el
30

Un simple XOR sobre los valores hash de las propiedades críticas es suficiente el 99% del tiempo.

Por ejemplo:

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

Solución encontrada en http://nshipster.com/equality/ por Mattt Thompson (¡que también mencionó esta pregunta en su publicación!)

Yariv Nissim
fuente
1
El problema con esta respuesta es que no toma en consideración los valores primitivos en absoluto. Y los valores primitivos también pueden ser importantes para el hash.
Vive
@Vive La mayoría de estos problemas se resuelven en Swift, pero estos tipos generalmente representan su propio hash ya que son primitivos.
Yariv Nissim
1
Si bien tienes razón para Swift, todavía hay muchos proyectos escritos con objc. Debido a que su respuesta está dedicada a objc, merece al menos una mención.
Vive
XORing valores hash juntos es un mal consejo, conduce a muchas colisiones hash. En su lugar, multiplique con un número primo y luego sume, como indican otras respuestas.
fishinear
27

Encontré este hilo extremadamente útil al proporcionar todo lo que necesitaba para implementar mis métodos isEqual:y hashcon un solo truco. Al probar las variables de instancia de objeto en isEqual:el código de ejemplo, se usa:

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

Esto falló repetidamente ( es decir , devolvió NO ) sin un error, cuando supe que los objetos eran idénticos en la prueba de mi unidad. La razón fue que una de las NSStringvariables de instancia era nula, por lo que la declaración anterior fue:

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

y dado que nil responderá a cualquier método, esto es perfectamente legal pero

[nil isEqual: nil]

devuelve nil , que es NO , por lo tanto, cuando tanto el objeto como el objeto de prueba tenían un objeto nil , se considerarían no iguales ( es decir , isEqual:devolverían NO ).

Esta solución simple fue cambiar la instrucción if a:

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

De esta manera, si sus direcciones son las mismas, omite la llamada al método, sin importar si ambas son nulas o si ambas apuntan al mismo objeto, pero si no es nula o apuntan a diferentes objetos, se llama al comparador de manera apropiada.

Espero que esto le ahorre a alguien unos minutos de rascarse la cabeza.

LavaSlider
fuente
20

La función hash debería crear un valor semi-único que no sea probable que choque o coincida con el valor hash de otro objeto.

Aquí está la función hash completa, que se puede adaptar a las variables de instancia de sus clases. Utiliza NSUInteger's en lugar de int para compatibilidad en aplicaciones de 64/32 bits.

Si el resultado se convierte en 0 para diferentes objetos, corre el riesgo de colisionar hashes. El colisionar hash puede provocar un comportamiento inesperado del programa cuando se trabaja con algunas de las clases de recopilación que dependen de la función hash. Asegúrese de probar su función hash antes de usarla.

-(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;
}
Paul Solt
fuente
3
Aquí hay uno: prefiero evitar la sintaxis de puntos, así que transformé su declaración BOOL en (por ejemplo) result = prime * result + [self isSelected] ? yesPrime : noPrime;. Luego descubrí que esto se estaba configurando resulten (por ejemplo) 1231, supongo que debido a que el ?operador tiene prioridad. He arreglado el problema agregando entre paréntesis:result = prime * result + ([self isSelected] ? yesPrime : noPrime);
Ashley
12

La manera fácil pero ineficiente es devolver lo mismo -hash valor para cada instancia. De lo contrario, sí, debe implementar hash basado solo en objetos que afectan la igualdad. Esto es complicado si utiliza comparaciones laxas -isEqual:(por ejemplo, comparaciones de cadenas que no distinguen entre mayúsculas y minúsculas). Para ints, generalmente puede usar el int en sí, a menos que se compare con NSNumbers.

No utilice | =, sin embargo, se saturará. Use ^ = en su lugar.

Dato curioso al azar: [[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]pero [[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]. (rdar: // 4538282, abierto desde el 05 de mayo de 2006)

Jens Ayton
fuente
1
Estás exactamente en el | =. Realmente no quise decir eso. :) + = y ^ = son bastante equivalentes. ¿Cómo manejas primitivas no enteras como double y float?
Dave Dribin el
Dato curioso al azar: Pruébelo en Snow Leopard ... ;-)
Quinn Taylor
Tiene razón al usar XOR en lugar de OR para combinar campos en un hash. Sin embargo, no use el consejo de devolver el mismo valor de sombreado para cada objeto, aunque es fácil, puede degradar severamente el rendimiento de cualquier cosa que use el hash del objeto. El hash no tiene que ser distinto para los objetos que no son iguales, pero si puede lograrlo, no hay nada igual.
Quinn Taylor el
El informe de error de radar abierto está cerrado. openradar.me/4538282 ¿Qué significa eso?
JJD
JJD, el error se corrigió en Mac OS X 10.6, como sugirió Quinn. (Tenga en cuenta que el comentario tiene dos años.)
Jens Ayton
9

Recuerde que solo necesita proporcionar hash que sea igual cuando isEqualsea ​​cierto. CuandoisEqual es falso, el hash no tiene que ser desigual, aunque presumiblemente lo sea. Por lo tanto:

Mantenga el hash simple. Elija una variable miembro (o pocos miembros) que sea la más distintiva.

Por ejemplo, para CLPlacemark, el nombre solo es suficiente. Sí, hay 2 o 3 distintivos CLPlacemark con exactamente el mismo nombre, pero son raros. Usa ese hash.

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

@implementation CLPlacemark (equal)

...

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


@end

Tenga en cuenta que no me molesto en especificar la ciudad, el país, etc. El nombre es suficiente. Quizás el nombre y la ubicación CL.

El hash se debe distribuir uniformemente. Por lo tanto, puede combinar varias variables de miembros utilizando el símbolo de intercalación ^ (signo xor)

Entonces es algo como

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

De esa manera, el hash se distribuirá uniformemente.

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

Entonces, ¿qué hacer en la matriz?

De nuevo, simple. No tiene que hacer hash a todos los miembros de la matriz. Lo suficiente como para trocear el primer elemento, el último elemento, el recuento, tal vez algunos elementos intermedios, y eso es todo.

user4951
fuente
Los valores hash de XORing no proporcionan una distribución uniforme.
fishinear
7

Espera, seguramente una forma mucho más fácil de hacer esto es anular primero - (NSString )description y proporcionar una representación de cadena del estado de su objeto (debe representar todo el estado de su objeto en esta cadena).

Luego, solo proporcione la siguiente implementación de hash:

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

Esto se basa en el principio de que "si dos objetos de cadena son iguales (según lo determinado por el método isEqualToString:), deben tener el mismo valor hash".

Fuente: Referencia de clase NSString

Jonathan Ellis
fuente
1
Esto supone que el método de descripción será único. El uso de hash de descripción crea una dependencia, que podría no ser obvia, y un mayor riesgo de colisiones.
Paul Solt
1
+1 Upvoted. Esta es una idea impresionante. Si temes que las descripciones causen colisiones, puedes anularlas.
user4951
Gracias Jim, no negaré que esto es un truco, pero funcionaría en cualquier caso que se me ocurra, y como dije, siempre que anules description, no veo por qué esto es inferior a cualquiera de las soluciones más votadas. Puede que no sea la solución matemáticamente más elegante, pero debería ser suficiente. Como Brian B. afirma (la respuesta más votada en este momento): "Trato de evitar crear nuevos algoritmos hash" - ¡de acuerdo! - Yo solo hashel NSString!
Jonathan Ellis
Votado porque es una buena idea. Sin embargo, no lo voy a usar porque temo las asignaciones adicionales de NSString.
karwag
1
Esta no es una solución genérica ya que para la mayoría de las clases descriptionincluye la dirección del puntero. Entonces, esto hace dos instancias diferentes de la misma clase que son iguales con diferentes hash, lo que viola la suposición básica de que dos objetos iguales tienen el mismo hash.
Diogo T
5

Los contratos de igualdad y hash están bien especificados e investigados a fondo en el mundo de Java (consulte la respuesta de @ mipardi), pero todas las mismas consideraciones deberían aplicarse a Objective-C.

Eclipse hace un trabajo confiable al generar estos métodos en Java, así que aquí hay un ejemplo de Eclipse portado a mano a 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;
}

Y para una subclase YourWidgetque agrega una propiedadserialNo :

- (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;
}

Esta implementación evita algunas dificultades de subclasificación en la muestra isEqual: de Apple:

  • La prueba de clase de Apple other isKindOfClass:[self class]es asimétrica para dos subclases diferentes de MyWidget. La igualdad debe ser simétrica: a = b si y solo si b = a. Esto podría solucionarse fácilmente cambiando la prueba aother isKindOfClass:[MyWidget class] , entonces todas las MyWidgetsubclases serían mutuamente comparables.
  • El uso de una isKindOfClass:prueba de subclase evita que las subclases se anulen isEqual:con una prueba de igualdad refinada. Esto se debe a que la igualdad debe ser transitiva: si a = by a = c, entonces b = c. Si una MyWidgetinstancia se compara igual a dos YourWidgetinstancias, entonces esas YourWidgetinstancias deben comparar igual entre sí, incluso si suserialNo difieren.

El segundo problema se puede solucionar solo considerando que los objetos son iguales si pertenecen exactamente a la misma clase, de ahí que [self class] != [object class] prueba aquí. Para las clases de aplicación típicas , este parece ser el mejor enfoque.

Sin embargo, ciertamente hay casos en los que la isKindOfClass:prueba es preferible. Esto es más típico de las clases de framework que de las clases de aplicación. Por ejemplo, cualquiera NSStringdebe comparar igual a cualquier otro NSStringcon la misma secuencia de caracteres subyacente, independientemente de NSString/NSMutableString distinción , y también independientemente de qué clases privadas NSStringestán involucradas en el grupo de clases.

En tales casos, isEqual:debe tener un comportamiento bien definido y bien documentado, y debe quedar claro que las subclases no pueden anular esto. En Java, la restricción de 'no anulación' se puede aplicar marcando los métodos igual y hashcode como final, pero Objective-C no tiene equivalente.

jedwidz
fuente
@adubr Eso está cubierto en mis últimos dos párrafos. No es focal ya que MyWidgetse entiende que no es un grupo de clases.
jedwidz
5

Esto no responde directamente a su pregunta (en absoluto), pero he usado MurmurHash antes para generar hashes: murmurhash

Supongo que debería explicar por qué: murmurhash es muy rápido ...

Schwa
fuente
2
Una biblioteca C ++ que se centra en hashes únicos para una clave nula * usando un número aleatorio (y que tampoco se relaciona con objetos Objective-C) realmente no es una sugerencia útil aquí. El método -hash debería devolver un valor constante cada vez, o será completamente inútil. Si el objeto se agrega a una colección que invoca -hash y devuelve un nuevo valor cada vez, nunca se detectarán duplicados y tampoco podrá recuperar el objeto de la colección. En este caso, el término "hash" es diferente del significado en seguridad / criptografía.
Quinn Taylor el
3
murmurhash no es una función hash criptográfica. Verifique sus hechos antes de publicar información incorrecta. Murmurhash podría ser útil para el hash personalizado de clases objetivo-c (especialmente si tienes muchos NSDatas involucrados) porque es extremadamente rápido. Sin embargo, le garantizo que tal vez sugiera que no es el mejor consejo para darle a alguien "simplemente recogiendo el objetivo-c", pero tenga en cuenta mi prefijo en mi respuesta original a la pregunta.
schwa
4

Soy un novato del Objetivo C también, pero encontré un excelente artículo sobre identidad vs. igualdad en el Objetivo C aquí . Según mi lectura, parece que podría mantener la función hash predeterminada (que debería proporcionar una identidad única) e implementar el método isEqual para que compare los valores de los datos.

ceperry
fuente
Soy un novato de Cocoa / Objective C, y esta respuesta y este enlace realmente me ayudaron a cortar todas las cosas más avanzadas de arriba a abajo. No necesito preocuparme por los hashes, solo implementando el método isEqual: ¡Gracias!
John Gallagher
No te pierdas el enlace de @ceperry. El artículo Equality vs Identityde Karl Kraft es realmente bueno.
JJD
66
@ John: Creo que deberías volver a leer el artículo. Dice muy claramente que "las instancias que son iguales deben tener valores hash iguales". Si anula isEqual:, también debe anular hash.
Steve Madsen
3

Quinn se equivoca al decir que la referencia al soplo de soplo es inútil aquí. Quinn tiene razón en que quiere comprender la teoría detrás del hash. El murmullo destila gran parte de esa teoría en una implementación. Merece la pena explorar cómo aplicar esa implementación a esta aplicación en particular.

Algunos de los puntos clave aquí:

La función de ejemplo de tcurdt sugiere que '31' es un buen multiplicador porque es primo. Hay que demostrar que ser primo es una condición necesaria y suficiente. De hecho, 31 (y 7) probablemente no sean primos particularmente buenos porque 31 == -1% 32. Es probable que un multiplicador impar con aproximadamente la mitad de los bits establecidos y la mitad de los bits libres sea mejor. (La constante de multiplicación hash murmullo tiene esa propiedad).

Este tipo de función hash probablemente sería más fuerte si, después de multiplicar, el valor del resultado se ajustara mediante un desplazamiento y xor. La multiplicación tiende a producir los resultados de muchas interacciones de bits en el extremo superior del registro y resultados de baja interacción en el extremo inferior del registro. El desplazamiento y xor aumentan las interacciones en el extremo inferior del registro.

Establecer el resultado inicial en un valor donde aproximadamente la mitad de los bits son cero y aproximadamente la mitad de los bits son uno también tendería a ser útil.

Puede ser útil tener cuidado con el orden en que se combinan los elementos. Probablemente, primero se deben procesar booleanos y otros elementos donde los valores no están fuertemente distribuidos.

Puede ser útil agregar un par de etapas de codificación de bits adicionales al final del cálculo.

Si el hasm murmurio es realmente rápido para esta aplicación es una pregunta abierta. El soplo de murmullo premezcla los bits de cada palabra de entrada. Se pueden procesar varias palabras de entrada en paralelo, lo que ayuda a los cpus canalizados de múltiples problemas.


fuente
3

Combinando la respuesta de @ tcurdt con la respuesta de @ oscar-gomez para obtener nombres de propiedades , podemos crear una solución fácil de colocar tanto para isEqual como para hash:

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

Ahora, en su clase personalizada, puede implementar isEqual:y hash:

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

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

Tenga en cuenta que si está creando un objeto que puede mutar después de la creación, el valor hash no debe cambiar si el objeto se inserta en una colección. Hablando en términos prácticos, esto significa que el valor hash debe fijarse desde el punto de creación inicial del objeto. Consulte la documentación de Apple sobre el método -hash del protocolo NSObject para obtener más información:

Si se agrega un objeto mutable a una colección que utiliza valores hash para determinar la posición del objeto en la colección, el valor devuelto por el método hash del objeto no debe cambiar mientras el objeto está en la colección. Por lo tanto, el método hash no debe depender de ninguna información del estado interno del objeto o debe asegurarse de que la información del estado interno del objeto no cambie mientras el objeto está en la colección. Así, por ejemplo, un diccionario mutable se puede poner en una tabla hash pero no debe cambiarlo mientras está allí. (Tenga en cuenta que puede ser difícil saber si un objeto determinado está o no en una colección).

Esto me parece una locura completa, ya que potencialmente hace que las búsquedas de hash sean mucho menos eficientes, pero supongo que es mejor errar por precaución y seguir lo que dice la documentación.

usuario10345
fuente
1
Estás leyendo mal los documentos hash: es esencialmente una situación de "uno u otro". Si el objeto cambia, el hash generalmente también cambia. Esto es realmente una advertencia para el programador, que si el hash cambia como resultado de la mutación de un objeto, entonces cambiar el objeto mientras reside en una colección que utiliza el hash causará un comportamiento inesperado. Si el objeto debe ser "mutable con seguridad" en tal situación, no tiene más remedio que hacer que el hash no esté relacionado con el estado mutable. Esa situación particular me suena extraña, pero ciertamente hay situaciones poco frecuentes en las que se aplica.
Quinn Taylor el
1

Lo siento si me arriesgo a hacer sonar un boffin completo aquí, pero ... ... nadie se molestó en mencionar que para seguir las 'mejores prácticas' definitivamente no deberías especificar un método igual que NO tomaría en cuenta todos los datos que posee tu objeto objetivo, por ejemplo los datos agregados a su objeto, en comparación con un asociado del mismo, deben tenerse en cuenta al implementar iguales. Si no desea tener en cuenta, diga 'edad' en una comparación, entonces debe escribir un comparador y usarlo para realizar sus comparaciones en lugar de isEqual :.

Si define un método isEqual: que realiza una comparación de igualdad arbitrariamente, corre el riesgo de que otro desarrollador, o incluso usted mismo, utilice este método una vez que haya olvidado el 'giro' en su interpretación igual.

Ergo, aunque esta es una gran pregunta sobre el hash, normalmente no es necesario redefinir el método de hash, probablemente deberías definir un comparador ad-hoc.

Thibaud de Souza
fuente