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.

39
demandé sur Community 2015-07-15 21:24:25

4 réponses

mise à Jour

Martin R écrit :

à partir de Swift 4.1 , le compilateur peut synthétiser Equatable et Hashable 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

  1. mettre en œuvre le Equatable protocol (hashable hérites from Equatable)
  2. retour a calculé hashValue

ces points découlent de l'axiome donné dans la documentation:

x == y implique x.hashValue == y.hashValue

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

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.

44
répondu Suragch 2018-09-26 00:27:17

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

4
répondu Kametrixom 2017-05-31 00:59:17

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

3
répondu Kametrixom 2015-07-15 18:57:05

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...

3
répondu Aaron Rasmussen 2015-07-15 19:16:13