UnicodeのUTF-32スカラー値のみを処理することを除いて、String
のように動作する構造を作成しています。したがって、それはUInt32
の配列です。 (背景については この質問 を参照してください。)
カスタムScalarString
構造体を辞書のキーとして使用できるようにしたい。例えば:
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
}
そのためには、ScalarString
で Hashable Protocol を実装する必要があります。私はこのようなことをできると思った:
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
}
しかし、私は Swift配列 にhashValue
がないことを発見しました。
記事 Swiftでハッシュ可能プロトコルを実装するための戦略 には多くの素晴らしいアイデアがありましたが、この場合にうまく機能すると思われるものは見当たりませんでした。具体的には、
hashValue
がありません)ここに私が読んだ他のいくつかのものがあります:
Swift Stringsには hashValue
プロパティがあるため、実行できることがわかっています。
カスタム構造のhashValue
を作成するにはどうすればよいですか?
更新1:String
に変換してからString
のhashValue
。独自の構造を作成するための私の全ポイントは、多くのString
変換を行わないようにすることでした。 String
は、どこかからhashValue
を取得します。私は同じ方法を使用してそれを得ることができるようです。
更新2:他のコンテキストからの文字列ハッシュコードアルゴリズムの実装を検討してきました。ただし、どちらが最適かを知り、それらをSwiftで表現するのは少し困難です。
hashCode
アルゴリズムUpdate 3
これらのことを行うのに推奨される方法でない限り、外部フレームワークをインポートしないことを希望します。
DJBハッシュ関数を使用して可能な解決策を提出しました。
マーティンR 書き込み :
Swift 4.1の時点で、すべてのメンバーがEquatable /に準拠している場合、コンパイラは
Equatable
およびHashable
を型適合のために自動的に合成できます。ハッシュ可能(SE0185)。そしてSwift 4.2の時点で、高品質のハッシュコンバイナーがSwift標準ライブラリ(SE -0206)。したがって、独自のハッシュ関数を定義する必要はもうありません。適合性を宣言するだけで十分です。
struct ScalarString: Hashable, ... { private var scalarArray: [UInt32] = [] // ... }
したがって、以下の答えは書き直す必要があります(まだ)。それが起こるまで、上記のリンクからのMartin Rの答えを参照してください。
コードレビューに対する元の回答 を送信した後、この回答は完全に書き直されました。
ハッシュ可能プロトコル を使用すると、カスタムクラスまたは構造体を辞書キーとして使用できます。このプロトコルを実装するには、次のことが必要です。
hashValue
を返しますこれらのポイントは、ドキュメントに記載されている公理に基づいています。
x == y
はx.hashValue == y.hashValue
を意味します
ここで、x
およびy
は、あるタイプの値です。
Equatableプロトコルを実装するには、タイプで==
(等価)演算子を使用する方法を定義します。あなたの例では、等価性は次のように決定できます。
func ==(left: ScalarString, right: ScalarString) -> Bool {
return left.scalarArray == right.scalarArray
}
==
関数はグローバルなので、クラスまたは構造体の外部に移動します。
hashValue
を返しますカスタムクラスまたは構造体には、計算されたhashValue
変数も必要です。適切なハッシュアルゴリズムは、広範囲のハッシュ値を提供します。ただし、ハッシュ値がすべて一意であることを保証する必要はないことに注意してください。 2つの異なる値に同一のハッシュ値がある場合、これはハッシュ衝突と呼ばれます。衝突がある場合は追加の作業が必要です(これが適切な分布が望ましい理由です)が、いくつかの衝突が予想されます。私が理解しているように、==
関数はその特別な仕事をします。 (Update: ==
は、all作業を行う可能性があるようです。 )
ハッシュ値を計算する方法はいくつかあります。たとえば、配列内の要素の数を返すなどの簡単なことを実行できます。
var hashValue: Int {
return self.scalarArray.count
}
これにより、2つの配列の要素数が同じで値が異なるたびにハッシュ衝突が発生します。 NSArray
は明らかにこのアプローチを使用しています。
DJBハッシュ関数
文字列で機能する一般的なハッシュ関数は、DJBハッシュ関数です。これは私が使用するものですが、他のいくつかをチェックしてください here 。
A Swift implementation @ MartinRにより提供 が続きます:
var hashValue: Int {
return self.scalarArray.reduce(5381) {
($0 << 5) &+ $0 &+ Int($1)
}
}
これは元の実装の改良版ですが、 reduce
に馴染みのない人にとって読みやすい古い拡張フォームも含めてみましょう。これは同等です、私は信じています:
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
}
&+
演算子を使用すると、Int
がオーバーフローし、長い文字列に対して最初からやり直すことができます。
これらの部分を見てきましたが、Hashableプロトコルに関連するサンプルコード全体を示します。 ScalarString
は、質問のカスタムタイプです。もちろん、これは人によって異なります。
// 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
}
Code ReviewのMartin Rに感謝します。私の書き換えは、主に 彼の答え に基づいています。これが役に立つと思ったら、彼に賛成票をください。
Swiftは現在オープンソースであるため、 ソースコード からhashValue
に対してString
がどのように実装されているかを確認できます。ここで示した答えよりも複雑なように見えますが、十分に時間をかけて分析していません。自分でそうしてください。
これは非常にエレガントなソリューションではありませんが、うまく機能します。
"\(scalarArray)".hashValue
または
scalarArray.description.hashValue
ハッシュソースとしてテキスト表現を使用するだけです
編集(17年5月31日):受け入れられた回答を参照してください。この答えはほとんどCommonCrypto
Frameworkの使用方法のデモンストレーションです
さて、先に進み、CommonCryptoフレームワークのSHA-256ハッシュアルゴリズムを使用して、すべての配列をHashable
プロトコルで拡張しました。あなたは置く必要があります
#import <CommonCrypto/CommonDigest.h>
これが機能するように、ブリッジングヘッダーに追加します。ただし、ポインタを使用する必要があるのは残念です。
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]
}
}
編集(17年5月31日):これをしないでください。SHA256にはハッシュの衝突はほとんどありませんが、ハッシュの等価性によって等価性を定義するのは間違った考えです
public func ==<T>(lhs: [T], rhs: [T]) -> Bool {
return lhs.hashValue == rhs.hashValue
}
これは、CommonCrypto
で得られるものと同じです。 ugいですが、高速で あまりないハッシュ衝突はほとんどありません
編集(15年7月15日):速度テストをいくつか行いました。
サイズnのランダムに埋められたInt
配列は、平均で1000回以上実行されました。
n -> time
1000 -> 0.000037 s
10000 -> 0.000379 s
100000 -> 0.003402 s
一方、文字列ハッシュ方式の場合:
n -> time
1000 -> 0.001359 s
10000 -> 0.011036 s
100000 -> 0.122177 s
したがって、SHA-256方式は、ストリング方式よりも約33倍高速です。文字列を使用することが非常に良い解決策であると言っているわけではありませんが、それを今と比較できる唯一のものです
1つの提案-String
をモデリングしているので、[UInt32]
配列をString
に変換し、String
のhashValue
を使用することはできますか?このような:
var hashValue : Int {
get {
return String(self.scalarArray.map { UnicodeScalar($0) }).hashValue
}
}
これにより、カスタムstruct
をString
sと比較することもできますが、それが良いアイデアかどうかは、何をしようとしているかによって異なります...
また、このアプローチを使用すると、ScalarString
の表現が標準的に同等である場合、hashValue
のインスタンスは同じString
を持つことに注意してください。
したがって、hashValue
が一意のString
を表すようにしたい場合、私のアプローチは良いと思います。 hashValue
がUInt32
値の一意のシーケンスを表すようにする場合、@ Kametrixomの答えは...