[英]How to implement the Hashable Protocol in Swift for an Int array (a custom string struct)
我正在制作一個類似於String
的結構,只是它只處理Unicode UTF-32標量值。 因此,它是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字符串具有hashValue
屬性,因此我知道可以做到這一點。
如何為自定義結構創建hashValue
?
更新1:我想做一些不涉及轉換為String
然后使用String
的hashValue
。 創建我自己的結構的全部目的是為了避免進行很多String
轉換。 String
從某處獲取其hashValue
。 看來我可以使用相同的方法來獲得它。
更新2:我一直在研究其他上下文中字符串哈希碼算法的實現。 不過,我很難知道哪種方法最好,並用Swift來表達它們。
hashCode
算法 更新3
我寧願不要導入任何外部框架,除非這是推薦用於這些事情的方法。
我使用DJB哈希函數提交了可能的解決方案。
馬丁·R 寫道 :
從Swift 4.1開始 ,如果所有成員都符合Equatable / Hashable(SE0185),則編譯器可以自動合成
Equatable
和Hashable
以實現類型一致性。 從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
變量。 一個好的哈希算法將提供廣泛的哈希值。 但是,應注意,您不必保證哈希值都是唯一的。 當兩個不同的值具有相同的哈希值時,這稱為哈希沖突。 發生沖突時,這需要一些額外的工作(這就是為什么需要良好的分布)的原因,但是某些沖突是可以預期的。 據我了解, ==
函數可以完成額外的工作。 ( 更新 : 看來==
可以完成所有工作。 )
有多種計算哈希值的方法。 例如,您可以做一些簡單的事情,就像返回數組中的元素數一樣。
var hashValue: Int {
return self.scalarArray.count
}
每當兩個數組具有相同數量的元素但值不同時,就會產生哈希沖突。 NSArray
顯然使用了這種方法。
DJB哈希函數
DJB哈希函數是與字符串一起使用的常見哈希函數。 這是我將要使用的那個,但是在這里請查看其他一些。
@MartinR提供的 Swift實現如下:
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
溢出並重新為長字符串重新開始。
我們已經看過各個部分,但現在讓我展示與哈希協議相關的整個示例代碼。 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中的MartinR。 我的改寫主要是基於他的回答 。 如果您覺得有幫助,請給他點贊。
Swift現在是開源的,因此可以從源代碼中了解如何為String
實現hashValue
。 它似乎比我在這里給出的答案更為復雜,並且我還沒有花時間對它進行全面分析。 自己動手做。
這不是一個很好的解決方案,但效果很好:
"\(scalarArray)".hashValue
要么
scalarArray.description.hashValue
只是使用文本表示作為哈希源
編輯(17年5月31日):請參閱接受的答案。 這個答案幾乎只是關於如何使用CommonCrypto
框架的演示。
好的,我取得了成功,並通過使用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
一樣好。 這是丑陋的,但速度快,
沒有多少
幾乎沒有哈希沖突肯定
編輯(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倍。 我並不是說使用字符串是一個很好的解決方案,但這是我們唯一可以與之比較的解決方案
一個建議-由於您正在建模一個String
,將[UInt32]
數組轉換為String
並使用String
的hashValue
是否hashValue
? 像這樣:
var hashValue : Int {
get {
return String(self.scalarArray.map { UnicodeScalar($0) }).hashValue
}
}
這可以方便地使您也將自定義struct
與String
進行比較,盡管這是否是一個好主意取決於您要執行的操作...
還要注意,使用這種方法,如果ScalarString
實例的String
表示形式是規范等效的,則它們將具有相同的hashValue
,這可能是您想要的,也可能不是您想要的。
因此,我想如果您希望hashValue
表示一個唯一的String
,那么我的方法會很好。 如果您希望hashValue
表示UInt32
值的唯一序列,則@Kametrixom的答案就是解決方法...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.