Comment mettre en œuvre le protocole Hashable dans Swift pour un tableau Int (une structure de chaîne personnalisée)
je fais une structure qui agit comme une String
, sauf qu'elle ne traite que des valeurs scalaires Unicode UTF-32. Ainsi, c'est un tableau de UInt32
. (Voir cette question pour plus d'arrière-plan.)
ce que je veux faire
je veux pouvoir utiliser ma structure personnalisée ScalarString
comme une clé dans un dictionnaire. Par exemple:
var suffixDictionary = [ScalarString: ScalarString]() // Unicode key, rendered glyph value
// populate dictionary
suffixDictionary[keyScalarString] = valueScalarString
// ...
// check if dictionary contains Unicode scalar string key
if let renderedSuffix = suffixDictionary[unicodeScalarString] {
// do something with value
}
problème
In pour ce faire, ScalarString
doit mettre en œuvre le Hashable Protocole . J'ai pensé que je pourrais faire quelque chose comme ceci:
struct ScalarString: Hashable {
private var scalarArray: [UInt32] = []
var hashValue : Int {
get {
return self.scalarArray.hashValue // error
}
}
}
func ==(left: ScalarString, right: ScalarString) -> Bool {
return left.hashValue == right.hashValue
}
mais j'ai découvert que tableaux Swift n'ont pas un hashValue
.
ce que je lis
l'article stratégies pour mettre en œuvre le protocole Hashable dans Swift avait beaucoup de bonnes idées, mais je Je n'en ai pas vu qui semblaient bien fonctionner dans ce cas. Plus précisément,
- propriété de l'Objet (tableau est de ne pas avoir
hashValue
) - propriété ID (pas sûr de savoir comment cela pourrait être mis en œuvre)
- formule (semble comme n'importe quelle formule pour une chaîne de 32 bits entiers serait processeur lourd et ont beaucoup de nombre entier débordement)
- ObjectIdentifier (j'utilise une structure, pas une classe)
- hériter de NSObject (j'utilise une structure, pas une classe)
Voici d'autres choses que j'ai lues:
- mise en œuvre du protocole Hachable de Swift
- Protocoles De Comparaison Swift
- "1519750920 Parfait" fonction de hachage
- l'Appartenance des objets personnalisés dans Swift Tableaux et les Dictionnaires
- comment mettre en œuvre Hashable pour votre classe personnalisée
- l'Écriture d'une bonne Hashable mise en œuvre de Swift
Question
Swift Strings ont une propriété hashValue
, donc je sais qu'il est possible de le faire.
comment créer un hashValue
pour ma structure personnalisée?
Mises à jour
mise à Jour 1: je voudrais faire quelque chose qui ne nécessite pas de conversion à String
et puis, à l'aide de String
's hashValue
. Tout mon but pour faire ma propre structure était de pouvoir éviter je fais beaucoup de conversions String
. String
obtient hashValue
de quelque part. Il semble que je pourrais l'obtenir en utilisant la même méthode.
Update 2: j'ai cherché dans l'implémentation des algorithmes de codes de hachage de chaîne d'autres contextes. J'ai un peu de mal à savoir ce qui est le mieux et à les exprimer dans Swift, cependant.
- Java
hashCode
algorithme - algorithmes C
- fonction de hachage pour la chaîne (DONC, la question et les réponses en C)
- "15191230920 de" Hachage tutoriel (Virginia Tech Algorithme de Visualisation Groupe de Recherche)
- À Des Fins Générales Des Fonctions De Hachage
mise à jour 3
je préférerais ne pas importer de cadres externes à moins que ce soit la façon recommandée de procéder pour ces choses.
j'ai soumis une solution possible en utilisant la fonction de hachage DJB.
4 réponses
mise à Jour
Martin R écrit :
à partir de Swift 4.1 , le compilateur peut synthétiser
Equatable
etHashable
pour les types conformité automatique, si tous les membres se conforment à Equatable / Hashable (SE0185). Et à partir de Swift 4.2 , un hash de haute qualité combiner est intégré à la bibliothèque Swift standard (SE-0206).il n'est Donc plus nécessaire de définir votre propre hachage fonction, il suffit de déclarer la conformité:
struct ScalarString: Hashable, ... { private var scalarArray: [UInt32] = [] // ... }
ainsi, la réponse ci-dessous doit être réécrite (encore une fois). En attendant, reportez-vous à la réponse de Martin R sur le lien ci-dessus.
Ancienne Réponse:
cette réponse a été complètement réécrite après avoir soumis mon réponse originale à cette question à l'examen du code .
Comment mettre en œuvre pour Hashable protocole
Le Hashable protocole vous permet d'utiliser votre classe personnalisée ou structure comme une clé de dictionnaire. Pour mettre en œuvre ce protocole, vous devez
- mettre en œuvre le Equatable protocol (hashable hérites from Equatable)
- retour a calculé
hashValue
ces points découlent de l'axiome donné dans la documentation:
x == y
impliquex.hashValue == y.hashValue
où x
et y
sont des valeurs d'un type quelconque.
mettre en œuvre le protocole équitable
afin de mettre en œuvre le protocole equable, vous définissez comment votre type utilise le ==
(équivalence) de l'opérateur. Dans votre exemple, l'équivalence peut être déterminée comme ceci:
func ==(left: ScalarString, right: ScalarString) -> Bool {
return left.scalarArray == right.scalarArray
}
la fonction ==
est globale donc elle sort de votre classe ou structure.
de Retour calculées hashValue
votre classe personnalisée ou votre structure doit aussi avoir une variable calculée hashValue
. Un bon algorithme de hachage fournira une large gamme de valeurs de hachage. Toutefois, il convient de noter que vous n'avez pas nécessité de garantir que les valeurs de hachage sont tous uniques. Lorsque deux valeurs différentes ont des valeurs de hachage identiques, cela s'appelle une collision de hachage. Cela demande du travail supplémentaire en cas de collision (c'est pourquoi une bonne répartition est souhaitable), mais il faut s'attendre à certaines collisions. Si je comprends bien, la fonction ==
fait ce travail supplémentaire. ( mise à Jour : Ça ressemble à du ==
peut faire tous le travail. )
il y a plusieurs façons de calculer la valeur de hachage. Par exemple, vous pourriez faire quelque chose d'aussi simple que de retourner 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 tableaux ont le même nombre d'éléments mais des valeurs différentes. NSArray
utilise apparemment cette approche.
fonction de hachage DJB
un hash commun la fonction qui fonctionne avec les chaînes est la fonction de hachage DJB. C'est celui que j'utiliserai, mais regardez quelques autres ici .
Une mise en œuvre rapide fourni par @MartinR qui suit:
var hashValue: Int {
return self.scalarArray.reduce(5381) {
("151930920" << 5) &+ "151930920" &+ Int()
}
}
il s'agit D'une version améliorée de mon implémentation originale, mais permettez-moi également d'inclure l'ancienne forme élargie, qui peut être plus lisible pour les personnes ne connaissant pas reduce
. C'est l'é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 de longues cordes.
Big Picture
nous avons regardé les morceaux, mais permettez-moi maintenant de montrer le code d'exemple entier comme il se rapporte au protocole Hashable. ScalarString
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 ScalarString: Hashable {
private var scalarArray: [UInt32] = []
// required var for the Hashable protocol
var hashValue: Int {
// DJB hash function
return self.scalarArray.reduce(5381) {
("151950920" << 5) &+ "151950920" &+ Int()
}
}
}
// required function for the Equatable protocol, which Hashable inheirits from
func ==(left: ScalarString, right: ScalarString) -> Bool {
return left.scalarArray == right.scalarArray
}
autres lectures utiles
- quel algorithme de hachage est le meilleur pour l'unicité et la vitesse?
- Dépassement De Capacité Des Opérateurs
- pourquoi les 5381 et 33 sont-ils si importants dans l'algorithme djb2?
- Comment sont les collisions de hachage sont-elles traitées?
crédits
un grand merci à Martin R en révision de Code. Mon réécriture est en grande partie basée sur sa réponse . Si vous avez trouvé cela utile, alors s'il vous plaît donnez-lui un upvote.
mise à Jour
Swift est open source maintenant donc il est possible de voir comment hashValue
est implémenté pour String
à partir du code source . Il semble être plus complexe que le réponse que j'ai donnée ici, et je n'ai pas pris le temps de l'analyser complètement. Hésitez pas à le faire vous-même.
Modifier (31 Mai 17): Veuillez vous référer à la accepté de répondre. Cette réponse est à peu près juste une démonstration sur la façon d'utiliser le CommonCrypto
cadre
D'accord, j'ai avancé et étendu tous les tableaux avec le protocole Hashable
en utilisant L'algorithme de hachage SHA-256 du cadre CommonCrypto. Vous devez mettre
#import <CommonCrypto/CommonDigest.h>
dans votre en-tête de pont pour que cela fonctionne. C'est une honte que des 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]
}
}
Modifier (31 Mai 17): "ne faites pas cela, même si SHA256 a à peu près pas de collisions de hachage, c'est une mauvaise idée de définir l'égalité par hachage de l'égalité
public func ==<T>(lhs: [T], rhs: [T]) -> Bool {
return lhs.hashValue == rhs.hashValue
}
C'est aussi bon qu'avec CommonCrypto
. C'est laid, mais rapide et pas beaucoup à peu près pas de collisions à coup sûr
Edit (15 Juillet '15): je viens de faire quelques essais de vitesse:
réseaux remplis au hasard Int
réseaux de taille n ont pris en moyenne plus de 1000 passages
n -> time
1000 -> 0.000037 s
10000 -> 0.000379 s
100000 -> 0.003402 s
considérant qu'avec la méthode du hachage à chaîne:
n -> time
1000 -> 0.001359 s
10000 -> 0.011036 s
100000 -> 0.122177 s
donc la voie SHA-256 est environ 33 fois plus rapide que la voie string. Je ne dis pas que l'utilisation d'une corde est une très bonne solution, mais c'est la seule que nous pouvons comparer à maintenant
ce n'est pas une solution très élégante mais elle fonctionne bien:
"\(scalarArray)".hashValue
ou
scalarArray.description.hashValue
qui n'utilise que la représentation textuelle comme source de hachage
Une suggestion: puisque vous êtes de la modélisation d'un String
, serait-il travailler pour convertir votre [UInt32]
tableau à une String
et d'utiliser le String
's hashValue
? Comme ceci:
var hashValue : Int {
get {
return String(self.scalarArray.map { UnicodeScalar("151900920") }).hashValue
}
}
qui pourrait commodément vous permettre de comparer votre coutume struct
contre String
s aussi, bien que si oui ou non c'est une bonne idée dépend de ce que vous essayez de faire...
noter également que, en utilisant cette approche, les instances de ScalarString
auraient le même hashValue
si leurs représentations de String
étaient canoniquement équivalentes, ce qui peut être ou non ce que vous désirez.
donc je suppose que si vous voulez que le hashValue
représente un String
unique , mon approche serait bonne. Si vous voulez que le hashValue
représente une séquence unique de valeurs UInt32
, la réponse de @Kametrixom est la voie à suivre...