Je crée une structure qui agit comme un 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'informations.)
Je veux pouvoir utiliser ma structure ScalarString
personnalisée comme 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
}
Pour ce faire, ScalarString
doit implémenter Hashable Protocol . Je pensais pouvoir faire quelque chose comme ça:
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 de hashValue
.
L'article Stratégies pour implémenter le 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 quelques autres choses que j'ai lues:
Les chaînes Swift ont une propriété hashValue
, donc je sais que c'est possible.
Comment créer un hashValue
pour ma structure personnalisée?
Mise à jour 1: Je voudrais faire quelque chose qui n'implique pas la conversion en String
puis l'utilisation de String
's hashValue
. Mon but pour créer ma propre structure était de pouvoir éviter de faire beaucoup de conversions String
. String
obtient son hashValue
de quelque part. Il semble que je pourrais l'obtenir en utilisant la même méthode.
Mise à jour 2: J'ai étudié l'implémentation d'algorithmes de codes de hachage de chaîne dans d'autres contextes. J'ai un peu de difficulté à savoir ce qui est le mieux et à les exprimer dans Swift, cependant.
hashCode
Mise à jour 3
Je préférerais ne pas importer de frameworks externes à moins que ce soit la voie recommandée pour ces choses.
J'ai soumis une solution possible en utilisant la fonction de hachage DJB.
Martin R écrit :
À partir de Swift 4.1 , le compilateur peut synthétiser
Equatable
etHashable
pour la conformité des types automatiquement, si tous les membres se conforment à Equatable/Hashable (SE0185). Et à partir de Swift 4.2 , un combineur de hachage de haute qualité est intégré dans la bibliothèque standard Swift (SE -0206).Il n'est donc plus nécessaire de définir votre propre fonction de hachage, 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 à partir du lien ci-dessus.
Cette réponse a été complètement réécrite après avoir soumis ma réponse originale à la révision du code .
Le protocole hashable vous permet d'utiliser votre classe ou structure personnalisée comme clé de dictionnaire. Afin de mettre en œuvre ce protocole, vous devez
hashValue
calculé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 certains types.
Afin d'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: ScalarString, right: ScalarString) -> Bool {
return left.scalarArray == right.scalarArray
}
La fonction ==
Est globale, elle sort donc de votre classe ou structure.
hashValue
calculéVotre classe ou structure personnalisée doit également avoir une variable hashValue
calculée. Un bon algorithme 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 distribution est souhaitable), mais certaines collisions sont à prévoir. Si je comprends bien, la fonction ==
Fait ce travail supplémentaire. ( Mise à jour : Il semble que ==
Puisse faire tout le travail. )
Il existe plusieurs façons de calculer la valeur de hachage. Par exemple, vous pouvez faire quelque chose d'aussi simple que de renvoyer 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 avaient 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 des chaînes est la fonction de hachage DJB. C'est celui que j'utiliserai, mais jetez un œil à d'autres ici .
A Swift fournie par @MartinR suit:
var hashValue: Int {
return self.scalarArray.reduce(5381) {
($0 << 5) &+ $0 &+ Int($1)
}
}
Il s'agit d'une version améliorée de mon implémentation d'origine, mais permettez-moi également d'inclure l'ancien formulaire développé, 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 chaînes.
Nous avons regardé les morceaux, mais permettez-moi maintenant de montrer tout l'exemple de code en ce qui concerne le 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) {
($0 << 5) &+ $0 &+ Int($1)
}
}
}
// required function for the Equatable protocol, which Hashable inheirits from
func ==(left: ScalarString, right: ScalarString) -> 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, veuillez lui donner une note positive.
Swift est maintenant open source, il est donc possible de voir comment hashValue
est implémenté pour String
à partir de code source . Elle semble plus complexe que la réponse que j'ai donnée ici et je n'ai pas pris le temps de l'analyser complètement. N'hésitez pas à le faire vous-même.
Ce n'est pas une solution très élégante mais cela fonctionne bien:
"\(scalarArray)".hashValue
ou
scalarArray.description.hashValue
Qui utilise simplement la représentation textuelle comme source de hachage
Edit (31 mai '17): Veuillez vous référer à la réponse acceptée. Cette réponse est à peu près juste une démonstration sur la façon d'utiliser le CommonCrypto
Framework
D'accord, j'ai pris de l'avance et étendu tous les tableaux avec le protocole Hashable
en utilisant l'algorithme de hachage SHA-256 du framework CommonCrypto. Vous devez mettre
#import <CommonCrypto/CommonDigest.h>
dans votre en-tête de pontage pour que cela fonctionne. C'est dommage que les pointeurs doivent être utilisés cependant:
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 la 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à peu près pas de collisions de hachage à coup sûr
Edit (15 juillet '15): Je viens de faire quelques tests de vitesse:
Int
tableaux de taille n remplis aléatoirement ont pris en moyenne plus de 1 000 exécutions
n -> time
1000 -> 0.000037 s
10000 -> 0.000379 s
100000 -> 0.003402 s
Alors qu'avec la méthode de hachage de chaîne:
n -> time
1000 -> 0.001359 s
10000 -> 0.011036 s
100000 -> 0.122177 s
La méthode SHA-256 est donc environ 33 fois plus rapide que la chaîne. Je ne dis pas que l'utilisation d'une chaîne est une très bonne solution, mais c'est la seule à laquelle nous pouvons la comparer en ce moment
Une suggestion - puisque vous modélisez un String
, est-ce que cela fonctionnerait pour convertir votre [UInt32]
tableau à un String
et utilisez le String
hashValue
? Comme ça:
var hashValue : Int {
get {
return String(self.scalarArray.map { UnicodeScalar($0) }).hashValue
}
}
Cela pourrait vous permettre de comparer facilement votre struct
personnalisé avec String
s, bien que cela soit une bonne idée ou non dépend de ce que vous essayez de faire ...
Notez également qu'en utilisant cette approche, les instances de ScalarString
auraient la même hashValue
si leurs représentations String
étaient canoniquement équivalentes, ce qui peut ou non être ce que vous désirez.
Je suppose donc que si vous voulez que le hashValue
représente un String
unique, mon approche serait bonne. Si vous souhaitez que hashValue
représente une séquence unique de UInt32
valeurs, la réponse de @ Kametrixom est la voie à suivre ...