[英]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.