Comment remplacez-vous correctement isEqual:
dans Objective-C? Le "catch" semble être que si deux objets sont égaux (comme déterminé par la isEqual:
méthode), ils doivent avoir la même valeur de hachage.
La section Introspection du Cocoa Fundamentals Guide contient un exemple sur la façon de remplacer isEqual:
, copié comme suit, pour une classe nommée 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;
}
Il vérifie l'égalité du pointeur, puis l'égalité des classes, et compare enfin les objets à l'aide isEqualToWidget:
, qui vérifie uniquement les propriétés name
et data
. Ce que l'exemple ne montre pas , c'est comment passer outre hash
.
Supposons qu'il existe d'autres propriétés qui n'affectent pas l'égalité, par exemple age
. Ne doit pas la hash
méthode est surchargée de sorte que seule name
et data
affecte le hachage? Et si oui, comment feriez-vous cela? Ajoutez simplement les hachages de name
et data
? Par exemple:
- (NSUInteger)hash {
NSUInteger hash = 0;
hash += [[self name] hash];
hash += [[self data] hash];
return hash;
}
Est-ce suffisant? Existe-t-il une meilleure technique? Et si vous avez des primitives, comme int
? Les convertir NSNumber
pour obtenir leur hachage? Ou des structures comme NSRect
?
( Brain fart : A l'origine écrit "bitwise OR" avec |=
. Signifie ajouter.)
la source
if (![other isKindOfClass:[self class]])
- Cela signifie techniquement que l'égalité ne sera pas commutative. C'est-à-dire A = B ne signifie pas B = A (par exemple si l'un est une sous-classe de l'autre)Réponses:
Commencer avec
Ensuite, pour chaque primitive que vous faites
Pour les objets, vous utilisez 0 pour nil et sinon leur code de hachage.
Pour les booléens, vous utilisez deux valeurs différentes
Explication et attribution
Ce n'est pas le travail de tcurdt, et les commentaires demandaient plus d'explications, donc je pense qu'une modification pour l'attribution est juste.
Cet algorithme a été popularisé dans le livre "Effective Java", et le chapitre correspondant est actuellement disponible en ligne ici . Ce livre a popularisé l'algorithme, qui est maintenant un défaut dans un certain nombre d'applications Java (y compris Eclipse). Il dérive cependant d'une implémentation encore plus ancienne, attribuée de diverses manières à Dan Bernstein ou Chris Torek. Cet ancien algorithme flottait à l'origine sur Usenet, et certaines attributions sont difficiles. Par exemple, il y a des commentaires intéressants dans ce code Apache (recherchez leurs noms) qui font référence à la source d'origine.
En fin de compte, il s'agit d'un algorithme de hachage très ancien et simple. Ce n'est pas le plus performant, et il n'est même pas prouvé mathématiquement être un "bon" algorithme. Mais c'est simple, et beaucoup de gens l'utilisent depuis longtemps avec de bons résultats, donc il a beaucoup de support historique.
la source
Je prends juste Objective-C moi-même, donc je ne peux pas parler spécifiquement pour cette langue, mais dans les autres langues que j'utilise si deux instances sont "égales", elles doivent renvoyer le même hachage - sinon vous allez avoir tout sortes de problèmes lorsque vous essayez de les utiliser comme clés dans une table de hachage (ou toute collection de type dictionnaire).
En revanche, si 2 instances ne sont pas égales, elles peuvent ou non avoir le même hachage - il est préférable qu'elles ne le soient pas. C'est la différence entre une recherche O (1) sur une table de hachage et une recherche O (N) - si tous vos hachages entrent en collision, vous trouverez peut-être que la recherche dans votre table n'est pas meilleure que la recherche dans une liste.
En termes de meilleures pratiques, votre hachage doit renvoyer une distribution aléatoire de valeurs pour son entrée. Cela signifie que, par exemple, si vous avez un double, mais que la majorité de vos valeurs ont tendance à se regrouper entre 0 et 100, vous devez vous assurer que les hachages renvoyés par ces valeurs sont uniformément répartis sur toute la plage de valeurs de hachage possibles. . Cela améliorera considérablement vos performances.
Il existe un certain nombre d'algorithmes de hachage, dont plusieurs sont répertoriés ici. J'essaie d'éviter de créer de nouveaux algorithmes de hachage car cela peut avoir de grandes implications sur les performances, donc utiliser les méthodes de hachage existantes et faire une combinaison au niveau du bit comme vous le faites dans votre exemple est un bon moyen de l'éviter.
la source
Par exemple:
Solution trouvée sur http://nshipster.com/equality/ par Mattt Thompson (qui a également référé cette question dans son article!)
la source
J'ai trouvé ce fil extrêmement utile fournissant tout ce dont j'avais besoin pour obtenir mes méthodes
isEqual:
ethash
implémentées avec une prise. Lors du test des variables d'instance d'objet dansisEqual:
l'exemple de code utilise:Cela a échoué à plusieurs reprises ( c.-à - d . Retourné NON ) sans erreur, quand j'ai su que les objets étaient identiques lors de mes tests unitaires. La raison en était que l'une des
NSString
variables d'instance était nulle, donc l'énoncé ci-dessus était:et puisque nul répondra à n'importe quelle méthode, c'est parfaitement légal mais
renvoie nil , qui est NO , donc lorsque l'objet et celui testé ont tous deux un objet nil , ils seront considérés comme différents ( c'est -à- dire
isEqual:
qu'ils renverraient NO ).Cette solution simple consistait à remplacer l'instruction if par:
De cette façon, si leurs adresses sont les mêmes, il ignore l'appel de méthode, peu importe si elles sont toutes deux nulles ou toutes deux pointant vers le même objet, mais si l'une n'est pas nulle ou si elles pointent vers des objets différents, le comparateur est appelé de manière appropriée.
J'espère que cela fera gagner quelques minutes à quelqu'un de se gratter la tête.
la source
La fonction de hachage doit créer une valeur semi-unique qui n'est pas susceptible d'entrer en collision ou de correspondre à la valeur de hachage d'un autre objet.
Voici la fonction de hachage complète, qui peut être adaptée aux variables d'instance de vos classes. Il utilise NSUInteger plutôt qu'int pour la compatibilité sur les applications 64 / 32bit.
Si le résultat devient 0 pour différents objets, vous courez le risque de heurter les hachages. La collision de hachages peut entraîner un comportement inattendu du programme lorsque vous travaillez avec certaines des classes de collection qui dépendent de la fonction de hachage. Assurez-vous de tester votre fonction de hachage avant de l'utiliser.
la source
result = prime * result + [self isSelected] ? yesPrime : noPrime;
. J'ai ensuite trouvé que ce paramètre était régléresult
sur (par exemple)1231
, je suppose en raison de la?
priorité de l' opérateur. J'ai résolu le problème en ajoutant des crochets:result = prime * result + ([self isSelected] ? yesPrime : noPrime);
Le moyen simple mais inefficace consiste à renvoyer la même
-hash
valeur pour chaque instance. Sinon, oui, vous devez implémenter un hachage basé uniquement sur des objets qui affectent l'égalité. C'est délicat si vous utilisez des comparaisons laxistes-isEqual:
(par exemple, des comparaisons de chaînes insensibles à la casse). Pour les ints, vous pouvez généralement utiliser l'int lui-même, sauf si vous comparez avec NSNumbers.N'utilisez pas | =, cependant, cela saturera. Utilisez ^ = à la place.
Fait amusant au hasard:,
[[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]
mais[[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]
. (rdar: // 4538282, ouvert depuis le 05-mai-2006)la source
N'oubliez pas que vous devez uniquement fournir un hachage égal quand
isEqual
c'est vrai. QuandisEqual
est faux, le hachage n'a pas à être inégal, bien que ce soit le cas. Par conséquent:Gardez le hachage simple. Choisissez une variable membre (ou quelques membres) qui est la plus distinctive.
Par exemple, pour CLPlacemark, le nom seul suffit. Oui, il y a 2 ou 3 CLPlacemark distincts portant exactement le même nom mais ceux-ci sont rares. Utilisez ce hachage.
...
Remarquez que je ne me donne pas la peine de préciser la ville, le pays, etc. Le nom suffit. Peut-être le nom et CLLocation.
Le hachage doit être réparti uniformément. Vous pouvez donc combiner plusieurs variables membres en utilisant le signe d'insertion ^ (signe xor)
C'est donc quelque chose comme
De cette façon, le hachage sera réparti uniformément.
Alors, que faire dans le tableau?
Encore une fois, simple. Vous n'avez pas besoin de hacher tous les membres du tableau. Assez pour hacher le premier élément, le dernier élément, le nombre, peut-être quelques éléments intermédiaires, et c'est tout.
la source
Attendez, sûrement un moyen beaucoup plus facile de le faire est d'abord de passer outre
- (NSString )description
et de fournir une représentation sous forme de chaîne de l'état de votre objet (vous devez représenter l'état complet de votre objet dans cette chaîne).Ensuite, fournissez simplement l'implémentation suivante de
hash
:Ceci est basé sur le principe que "si deux objets chaîne sont égaux (comme déterminé par la méthode isEqualToString:), ils doivent avoir la même valeur de hachage."
Source: Référence de classe NSString
la source
description
, je ne vois pas pourquoi cela est inférieur à l'une des solutions les plus votées. Peut-être pas la solution la plus élégante mathématiquement, mais devrait faire l'affaire. Comme le déclare Brian B. (réponse la plus votée à ce stade): "J'essaie d'éviter de créer de nouveaux algorithmes de hachage" - d'accord! - Je viens dehash
leNSString
!description
l'adresse du pointeur est incluse. Cela fait donc deux instances différentes de la même classe qui sont égales avec un hachage différent, ce qui viole l'hypothèse de base que deux objets égaux ont le même hachage!Les contrats d'égalité et de hachage sont bien spécifiés et font l'objet de recherches approfondies dans le monde Java (voir la réponse de @ mipardi), mais les mêmes considérations devraient s'appliquer à Objective-C.
Eclipse fait un travail fiable pour générer ces méthodes en Java, voici donc un exemple Eclipse porté manuellement à Objective-C:
Et pour une sous-classe
YourWidget
qui ajoute une propriétéserialNo
:Cette implémentation évite certains pièges de sous-classement dans l'exemple
isEqual:
d'Apple:other isKindOfClass:[self class]
est asymétrique pour deux sous-classes différentes deMyWidget
. L'égalité doit être symétrique: a = b si et seulement si b = a. Cela pourrait facilement être corrigé en changeant le test enother isKindOfClass:[MyWidget class]
, alors toutes lesMyWidget
sous-classes seraient mutuellement comparables.isKindOfClass:
test de sous-classe empêche les sous-classes de remplacerisEqual:
par un test d'égalité affiné. En effet, l'égalité doit être transitive: si a = b et a = c alors b = c. Si uneMyWidget
instance se compare à deuxYourWidget
instances, cesYourWidget
instances doivent alors se comparer égales les unes aux autres, même si leursserialNo
différences.Le deuxième problème peut être résolu en ne considérant les objets comme égaux que s'ils appartiennent exactement à la même classe, d'où le
[self class] != [object class]
test ici. Pour les classes d'application typiques , cela semble être la meilleure approche.Cependant, il y a certainement des cas où le
isKindOfClass:
test est préférable. Ceci est plus typique des classes de framework que des classes d'application. Par exemple, toutNSString
doit être égal à n'importe quel autreNSString
avec la même séquence de caractères sous-jacente, quelle que soit la distinctionNSString
/NSMutableString
, et quelles que soient les classes privées duNSString
cluster de classes impliquées.Dans de tels cas,
isEqual:
devrait avoir un comportement bien défini et bien documenté, et il devrait être clair que les sous-classes ne peuvent pas remplacer cela. En Java, la restriction 'no override' peut être appliquée en signalant les méthodes equals et hashcode commefinal
, mais Objective-C n'a pas d'équivalent.la source
MyWidget
est entendu que ce n'est pas un cluster de classe.Cela ne répond pas directement à votre question (du tout) mais j'ai déjà utilisé MurmurHash pour générer des hachages: murmurhash
Je suppose que je devrais expliquer pourquoi: le murmurhash est sanglant rapidement ...
la source
J'ai trouvé que cette page était un guide utile pour remplacer les méthodes de type égal et de hachage. Il comprend un algorithme décent pour calculer les codes de hachage. La page est orientée vers Java, mais il est assez facile de l'adapter à Objective-C / Cocoa.
la source
Je suis aussi un débutant en Objective C, mais j'ai trouvé un excellent article sur l'identité vs l'égalité dans Objective C ici . D'après ma lecture, il semble que vous pourriez simplement conserver la fonction de hachage par défaut (qui devrait fournir une identité unique) et implémenter la méthode isEqual afin de comparer les valeurs de données.
la source
Equality vs Identity
de Karl Kraft est vraiment bon.isEqual:
, vous devez également remplacerhash
.Quinn a tout simplement tort que la référence au hachage murmure soit inutile ici. Quinn a raison de vouloir comprendre la théorie du hachage. Le murmure distille une grande partie de cette théorie dans une implémentation. Il est intéressant d'explorer comment appliquer cette implémentation à cette application particulière.
Certains des points clés ici:
L'exemple de fonction de tcurdt suggère que «31» est un bon multiplicateur car il est premier. Il faut montrer qu'être premier est une condition nécessaire et suffisante. En fait, 31 (et 7) ne sont probablement pas des nombres premiers particulièrement bons, car 31 == -1% 32. Un multiplicateur impair avec environ la moitié des bits définis et la moitié des bits effacés est susceptible d'être meilleur. (La constante de multiplication de hachage de murmure a cette propriété.)
Ce type de fonction de hachage serait probablement plus fort si, après multiplication, la valeur du résultat était ajustée via un décalage et un xor. La multiplication a tendance à produire les résultats de nombreuses interactions binaires à l'extrémité supérieure du registre et de faibles résultats d'interaction à l'extrémité inférieure du registre. Le décalage et le xor augmentent les interactions à l'extrémité inférieure du registre.
La définition du résultat initial à une valeur où environ la moitié des bits sont nuls et environ la moitié des bits sont un aurait également tendance à être utile.
Il peut être utile de faire attention à l'ordre dans lequel les éléments sont combinés. On devrait probablement d'abord traiter les booléens et d'autres éléments où les valeurs ne sont pas fortement distribuées.
Il peut être utile d'ajouter quelques étapes de brouillage supplémentaires à la fin du calcul.
Que le hachage de murmure soit ou non rapide pour cette application est une question ouverte. Le hachage de murmure prémélange les bits de chaque mot d'entrée. Plusieurs mots d'entrée peuvent être traités en parallèle, ce qui aide les processeurs pipelinés à plusieurs problèmes.
la source
En combinant la réponse de @ tcurdt avec la réponse de @ oscar-gomez pour obtenir les noms de propriété , nous pouvons créer une solution de dépôt facile pour isEqual et hash:
Maintenant, dans votre classe personnalisée, vous pouvez facilement implémenter
isEqual:
ethash
:la source
Notez que si vous créez un objet qui peut être muté après la création, la valeur de hachage ne doit pas changer si l'objet est inséré dans une collection. En pratique, cela signifie que la valeur de hachage doit être fixée à partir du point de la création initiale de l'objet. Voir la documentation d'Apple sur la méthode -hash du protocole NSObject pour plus d'informations:
Cela me semble être un détournement complet, car il rend potentiellement les recherches de hachage beaucoup moins efficaces, mais je suppose qu'il est préférable de faire preuve de prudence et de suivre ce que dit la documentation.
la source
Désolé si je risque de sonner un boffin complet ici mais ... ... personne n'a pris la peine de mentionner que pour suivre les "meilleures pratiques", vous ne devriez certainement pas spécifier une méthode égale qui ne prendrait PAS en compte toutes les données appartenant à votre objet cible, par exemple quoi que ce soit les données sont agrégées à votre objet, par rapport à un associé de celui-ci, doivent être prises en compte lors de la mise en œuvre d'égal à égal. Si vous ne voulez pas prendre en compte, dites «âge» dans une comparaison, alors vous devriez écrire un comparateur et l'utiliser pour effectuer vos comparaisons au lieu de isEqual :.
Si vous définissez une méthode isEqual: qui effectue arbitrairement la comparaison d'égalité, vous courez le risque que cette méthode soit utilisée à mauvais escient par un autre développeur, ou même par vous-même, une fois que vous avez oublié le «rebondissement» dans votre interprétation égale.
Ergo, bien qu'il s'agisse d'un grand Q & A sur le hachage, vous n'avez normalement pas besoin de redéfinir la méthode de hachage, vous devriez probablement définir un comparateur ad hoc à la place.
la source