La mise en œuvre de hachage / -isEqual: / -isEqualTo...: pour Objective-C collections

Remarque: Le suivant de SORTE que les questions sont liées, mais ni eux, ni les ressources liées semblent pleinement répondre à mes questions, notamment en matière de mise en œuvre de tests d'égalité pour des collections d'objets.


Arrière-plan

NSObject fournit par défaut implémentations de -hash (qui renvoie l'adresse de l'instance, comme (NSUInteger)self) et -isEqual: (qui renvoie NO à moins que les adresses du destinataire et le paramètre sont identiques). Ces méthodes sont destinées à être remplacées si nécessaire, mais la documentation indique clairement que vous devez fournir deux ou aucun des deux. De plus, si -isEqual: retourne YES pour les deux objets, alors le résultat de -hash pour ces objets doit être le même. Si pas, des problèmes peuvent se produire lorsque des objets qui doivent être les mêmes — comme deux instances de chaîne pour laquelle -compare: retourne NSOrderedSame — sont ajoutés à une collection de Cacao ou de comparer directement.

Contexte

Je développe CHDataStructures.cadre, un open-source de la bibliothèque de l'Objective-C des structures de données. J'ai mis en œuvre un certain nombre de collections, et je suis actuellement en train d'affiner et d'améliorer leur fonctionnalité. Une des fonctionnalités que je veux ajouter, c'est la capacité à comparer des collections pour l'égalité avec l'autre.

Plutôt que de comparer uniquement les adresses de mémoire, ces comparaisons doivent considérer les objets présents dans les deux collections (y compris la commande, le cas échéant). Cette approche a un précédent dans le Cacao, et utilise généralement une méthode distincte, y compris les suivantes:

Je veux faire mes collections personnalisées robuste pour les tests d'égalité, de sorte qu'ils peuvent en toute sécurité (et prévisible) sera ajoutée à d'autres collections, et de permettre à d'autres personnes (comme un NSSet) pour déterminer si les deux collections sont égaux/équivalent/doublons.

Problèmes

Un -isEqualTo...: méthode fonctionne très bien sur son propre, mais les classes qui définissent ces méthodes est généralement aussi remplacer -isEqual: d'invoquer [self isEqualTo...:] si le paramètre est de la même classe (ou peut-être sous-classe) comme récepteur, ou [super isEqual:] autrement. Cela signifie que la classe doit également définir -hash tel qu'il sera de retour la même valeur pour les disparates instances qui ont le même contenu.

En outre, la documentation d'Apple pour -hash dispose ce qui suit: (l'emphase est mienne)

"Si une mutable objet est ajouté à une collection qui utilise les valeurs de hachage pour déterminer la position de l'objet dans la collecte, la valeur retournée par la méthode de hachage de l'objet ne doit pas changer pendant que l'objet est dans la collection. Par conséquent, soit la méthode de hachage ne doit pas compter sur l'objet l'état interne de l'information ou vous devez vous assurer que l'objet interne de l'état de l'information ne change pas alors que l'objet est dans la collection. Ainsi, par exemple, une mutable dictionnaire peut être mis dans une table de hachage, mais vous ne devez pas modifier, alors qu'il est là. (Notez qu'il peut être difficile de savoir si un objet est dans une collection)."

Edit: j'ai certainement comprendre pourquoi cela est nécessaire et tout à fait d'accord avec le raisonnement — je mentionné ici pour fournir un contexte supplémentaire, et a frôlé le sujet de pourquoi c'est le cas pour des raisons de concision.

Toutes mes collections sont mutables, et le hachage devra tenir compte d'au moins certains du contenu, donc la seule option ici est à considérer comme une erreur de programmation pour muter une collection stockée dans une autre collection. (Mes collections tous adopter NSCopying, de sorte collections comme NSDictionary peut réussir à faire une copie pour l'utiliser comme une clé, etc.)

Il fait sens pour moi, pour mettre en œuvre -isEqual: et -hash, puisque (par exemple) indirecte de l'utilisateur de l'une de mes classes ne peut pas savoir spécifique -isEqualTo...: méthode à appeler, ou même se préoccuper de savoir si deux objets sont des instances de la même classe. Ils devraient être en mesure d'appeler -isEqual: ou -hash sur toute variable de type id et obtenir le résultat escompté.

Contrairement à -isEqual: (qui a accès aux deux instances en cours de comparaison), -hash doit renvoyer un résultat "à l'aveuglette", qui n'ont accès qu'aux données à l'intérieur d'une instance particulière. Car il ne peut pas savoir ce que le hachage est utilisée, le résultat doit être uniforme pour tous possible instances qui devrait être considéré comme l'égal/identique, et doit toujours être d'accord avec -isEqual:. (Edit: Ce qui a été démenti par les réponses ci-dessous, et cela rend la vie plus facile.) En outre, la rédaction d'un bon de fonctions de hachage est non-trivial — garantir l'unicité est un défi, surtout quand vous avez seulement une NSUInteger (32/64 bits) pour la représenter.

Questions

  1. Sont là les meilleures pratiques lors de la mise en œuvre de de comparaison d'égalité -hash pour les collections?
  2. Sont là toutes les particularités de plan pour en Objective-C et Cocoa-esque collections?
  3. Y a de bonnes approches pour les tests unitaires -hash avec un degré raisonnable de confiance?
  4. Des suggestions sur la mise en œuvre de -hash d'accord avec -isEqual: pour les collections contenant des éléments de types arbitraires? Quels sont les pièges dois-je connaître? (Edit: Pas aussi problématique que j'ai d'abord pensé que @kperryua le souligne, "l'égalité des -hash valeurs ne pas implique -isEqual:".)

Edit: je devrais avoir précisé que je ne suis pas confus sur la façon de mettre en place des isEqual: ou -isEqualTo...: pour les collections, c'est simple. Je pense que ma confusion découle principalement de (à tort) à penser que le hachage DOIT renvoyer une valeur différente si -isEqual: retourne PAS. Ayant fait de la cryptographie dans le passé, je pensais que les hachages pour différentes valeurs DOIVENT être différentes. Cependant, les réponses ci-dessous m'a fait réaliser qu'une "bonne" fonction de hachage est vraiment sur le minimisant seau de collisions et de chaînage pour les collections qui utilisent -hash. Tandis que les hachages sont préférables, ils ne sont pas d'une exigence stricte.