Je fais une structure qui agit comme une Ssortingng
, sauf qu'elle ne traite que des valeurs scalaires UTF-32 Unicode. Ainsi, c'est un tableau de UInt32
. (Voir cette question pour plus de context.)
Je veux être en mesure d'utiliser ma structure ScalarSsortingng
personnalisée en tant que key dans un dictionary. Par exemple:
var suffixDictionary = [ScalarSsortingng: ScalarSsortingng]() // Unicode key, rendered glyph value // populate dictionary suffixDictionary[keyScalarSsortingng] = valueScalarSsortingng // ... // check if dictionary contains Unicode scalar ssortingng key if let renderedSuffix = suffixDictionary[unicodeScalarSsortingng] { // do something with value }
Pour ce faire, ScalarSsortingng
doit implémenter le protocole Hashable . Je pensais que je serais capable de faire quelque chose comme ça:
struct ScalarSsortingng: Hashable { private var scalarArray: [UInt32] = [] var hashValue : Int { get { return self.scalarArray.hashValue // error } } } func ==(left: ScalarSsortingng, right: ScalarSsortingng) -> Bool { return left.hashValue == right.hashValue }
mais ensuite j'ai découvert que les arrays Swift n'ont pas de hashValue
.
L'article Stratégies pour la mise en œuvre du protocole Hashable dans Swift avait beaucoup de bonnes idées, mais je n'en ai vu aucune qui semblait fonctionner correctement dans ce cas. Plus précisément,
hashValue
) Voici d'autres choses que je lis:
Swift Ssortingngs possède une propriété hashValue
, donc je sais que c'est possible.
Comment créer une hashValue
pour ma structure personnalisée?
Mise à jour 1: Je voudrais faire quelque chose qui n'implique pas de convertir en Ssortingng
, puis en utilisant hashValue
. Mon sharepoint vue pour créer ma propre structure était que je pouvais éviter de faire beaucoup de conversions de Ssortingng
. Ssortingng
obtient son hashValue
de quelque part. Il semble que je pourrais l'get en utilisant la même méthode.
Mise à jour 2: J'ai étudié la mise en œuvre des algorithms de hachage de strings d'autres contexts. J'ai un peu de mal à savoir lequel est le meilleur et à les exprimer dans Swift, cependant.
hashCode
algorithm Mise à jour 3
Je préférerais ne pas importer de frameworks externes à less que ce soit la façon recommandée d'aller pour ces choses.
J'ai soumis une solution possible en utilisant la fonction DJB Hash.
Cette réponse a été complètement réécrite après avoir soumis ma réponse originale à l'examen du code .
Le protocole Hashable vous permet d'utiliser votre class ou structure personnalisée en tant que key de dictionary. Afin de mettre en œuvre ce protocole, vous devez
hashValue
calculée Ces points découlent de l'axiome donné dans la documentation:
x == y
impliquex.hashValue == y.hashValue
où x
et y
sont des valeurs de type.
Pour implémenter le protocole Equatable, vous définissez comment votre type utilise l'opérateur ==
(équivalence). Dans votre exemple, l'équivalence peut être déterminée comme ceci:
func ==(left: ScalarSsortingng, right: ScalarSsortingng) -> Bool { return left.scalarArray == right.scalarArray }
La fonction ==
est globale donc elle sort de votre class ou structure.
hashValue
calculée Votre class ou structure personnalisée doit également avoir une variable hashValue
calculée. Un bon algorithm de hachage fournira une large gamme de valeurs de hachage. Cependant, il convient de noter que vous n'avez pas besoin de garantir que les valeurs de hachage sont toutes uniques. Lorsque deux valeurs différentes ont des valeurs de hachage identiques, cela s'appelle une collision de hachage. Cela nécessite un travail supplémentaire en cas de collision (c'est pourquoi une bonne dissortingbution est souhaitable), mais certaines collisions sont à prévoir. Si je comprends bien, la fonction ==
fait ce travail supplémentaire. ( Mise à jour : Il semble que ==
peut faire tout le travail. )
Il existe plusieurs façons de calculer la valeur de hachage. Par exemple, vous pourriez faire quelque chose d'aussi simple que de returnner le nombre d'éléments dans le tableau.
var hashValue: Int { return self.scalarArray.count }
Cela donnerait une collision de hachage chaque fois que deux arrays ont le même nombre d'éléments mais des valeurs différentes. NSArray
utilise apparemment cette approche.
Fonction de hachage DJB
Une fonction de hachage commune qui fonctionne avec les strings est la fonction de hachage DJB. C'est celui que je vais utiliser, mais vérifiez d'autres ici .
Une implémentation Swift fournie par @MartinR suit:
var hashValue: Int { return self.scalarArray.reduce(5381) { ($0 << 5) &+ $0 &+ Int($1) } }
Ceci est une version améliorée de mon implémentation d'origine, mais permettez-moi d'inclure également le formulaire développé plus ancien, qui peut être plus lisible pour les personnes qui ne connaissent pas reduce
. C'est équivalent, je crois:
var hashValue: Int { // DJB Hash Function var hash = 5381 for(var i = 0; i < self.scalarArray.count; i++) { hash = ((hash << 5) &+ hash) &+ Int(self.scalarArray[i]) } return hash }
L'opérateur &+
permet à Int
de déborder et de recommencer pour les longues strings.
Nous avons regardé les pièces, mais laissez-moi maintenant montrer l'set du code d'exemple en ce qui concerne le protocole Hashable. ScalarSsortingng
est le type personnalisé de la question. Ce sera différent pour différentes personnes, bien sûr.
// Include the Hashable keyword after the class/struct name struct ScalarSsortingng: Hashable { private var scalarArray: [UInt32] = [] // required var for the Hashable protocol var hashValue: Int { // DJB hash function return self.scalarArray.reduce(5381) { ($0 << 5) &+ $0 &+ Int($1) } } } // required function for the Equatable protocol, which Hashable inheirits from func ==(left: ScalarSsortingng, right: ScalarSsortingng) -> Bool { return left.scalarArray == right.scalarArray }
Un grand merci à Martin R dans Code Review. Ma réécriture est largement basée sur sa réponse . Si vous avez trouvé cela utile, alors s'il vous plaît lui donner un upvote.
Swift est open source maintenant il est donc possible de voir comment hashValue
est implémenté pour Ssortingng
partir du code source . Cela semble être plus complexe que la réponse que j'ai donnée ici, et je n'ai pas pris le time de l'parsingr complètement. N'hésitez pas à le faire vous-même.
Une suggestion – puisque vous modélisez une Ssortingng
, cela fonctionnerait-il pour convertir votre tableau [UInt32]
en Ssortingng
et utiliser le hashValue
? Comme ça:
var hashValue : Int { get { return Ssortingng(self.scalarArray.map { UnicodeScalar($0) }).hashValue } }
Cela pourrait facilement vous permettre de comparer votre struct
personnalisée à celle de Ssortingng
s, même si cela dépend de ce que vous essayez de faire …
Notez également que, en utilisant cette approche, les instances de ScalarSsortingng
auraient la même valeur hashValue
si leurs représentations Ssortingng
étaient canoniquement équivalentes, ce qui peut être ou ne pas être ce que vous désirez.
Donc je suppose que si vous voulez que hashValue
représente une Ssortingng
unique, mon approche serait bonne. Si vous voulez que hashValue
représente une séquence unique de valeurs UInt32, la réponse de @ Kamesortingxom est la voie à suivre …
Edit (31 mai '17): Veuillez vous référer à la réponse acceptée. Cette réponse est à peu près une démonstration sur l'utilisation de CommonCrypto
Framework
D'accord, je suis Hashable
l'avant et étendu tous les arrays avec le protocole Hashable
en utilisant l'algorithm de hachage SHA-256 de la structure CommonCrypto. Vous devez mettre
#import <CommonCrypto/CommonDigest.h>
dans votre en-tête de pontage pour que cela fonctionne. Il est dommage que les pointeurs doivent être utilisés si:
extension Array : Hashable, Equatable { public var hashValue : Int { var hash = [Int](count: Int(CC_SHA256_DIGEST_LENGTH) / sizeof(Int), repeatedValue: 0) withUnsafeBufferPointer { ptr in hash.withUnsafeMutableBufferPointer { (inout hPtr: UnsafeMutableBufferPointer<Int>) -> Void in CC_SHA256(UnsafePointer<Void>(ptr.baseAddress), CC_LONG(count * sizeof(Element)), UnsafeMutablePointer<UInt8>(hPtr.baseAddress)) } } return hash[0] } }
Edit (31 mai '17): Ne faites pas cela, même si SHA256 n'a pratiquement pas de collisions de hachage, c'est une mauvaise idée de définir l'égalité par l'égalité de hachage
public func ==<T>(lhs: [T], rhs: [T]) -> Bool { return lhs.hashValue == rhs.hashValue }
C'est aussi bon qu'avec CommonCrypto
. C'est moche, mais rapide et pas beaucoup de collisions de hachage à peu près à coup sûr
Edit (15 juillet 15): Je viens de faire quelques tests de vitesse:
Les arrays Int
de taille n, pris au hasard, ont pris en moyenne plus de 1000 passages
n -> time 1000 -> 0.000037 s 10000 -> 0.000379 s 100000 -> 0.003402 s
Alors qu'avec la méthode de hachage de la string:
n -> time 1000 -> 0.001359 s 10000 -> 0.011036 s 100000 -> 0.122177 s
Ainsi, la manière SHA-256 est environ 33 fois plus rapide que la manière de la corde. Je ne dis pas que l'utilisation d'une string est une très bonne solution, mais c'est la seule que l'on puisse comparer
Ce n'est pas une solution très élégante mais ça marche bien:
"\(scalarArray)".hashValue
ou
scalarArray.description.hashValue
Qui utilise simplement la représentation textuelle comme source de hachage