在 Swift 中实现字典
虽然 Swift 原生的字典类型实现的很复杂(毫无疑问是为了性能),但是我们可以利用 Swift 提供的工具写出漂亮简洁的实现。我们从一个简单的实现开始,并且逐步添加功能。 我们简要看一下字典的工作原理:它通过任意类型的关键字来设置和获取值。这些值常常存储在一个数组中,当然也可以存储在树型结构中。由于我还不太清楚以树作为字典存储结构的工作原理,所以这篇文章中我们主要探索一个由数组存储值的字典。 由于我们字典中的值是由数组存储,所以我们需要将给定的关键字转换成整数,然后让这个整数落在数组范围内。这两个方法一个是哈希函数,一个是取模操作。通过哈希函数,我们可以将关键字(通常是字符串,也可以是遵循
我从 让我们开始吧。我们知道 struct Dictionary<Key,Value where Key: Hashable> { private var storage = //some Array... subscript(key: Key) -> Value? { get { return nil } set { } } } 这是我们类型的基本结构。我们知道我们的数组必须是支持泛型的,但是还不知道具体的值是什么。由于两个不同的关键字经过哈希和取模操作后可能会指向数组的同一个位置,这样就会造成冲突,因此每个占位对象需要支持存储多个值。现在让来我们设计 struct Placeholder<Key,Value where Key: Hashable> { var values: [(Key,Value)] = [] } 这个对象存储很多经过哈希和取模操作后指向字典同一位置的键和值。如果我们很好的设计了字典,那么每个 现在我们大体知道了 private var storage = Array(count: 8,repeatedValue: Placeholder<Key,Value>()) 初始时我们给数组一个随机的大小。最好选择 2 的整数幂,因为对 2 的幂取模要比其他数更快。除非我们实现如何重新计算数组容量,否则取多大都没有关系。 为了设置字典的值,我们需要对键进行哈希(然后取绝对值,因为哈希有时会返回负值),然后根据数组大小进行取模操作。 set { let position = abs(key.hashValue) % storage.count //... } 在真正给字典添加值之前,我们需要 set { guard let value = newValue else { return } let position = abs(key.hashValue) % storage.count storage[position].values.append((key,value)) } 这就是基本满足我们需求的设置方法的实现。(我忽略掉了一些小的细节,比如当你试着对同一个键设置两次时会发生什么。我们很快会处理这种情况。) 取值的过程相当简单。对键进行哈希,取绝对值,取模操作,然后我们获取了在那个位置上的占位对象。我将会把从占位对象中获取值的过程交给占位对象自己去做。 get { let position = abs(key.hashValue) % storage.count return storage[position].firstValue(matchingKey: key) } 真正神奇的是在下面这个方法。我们需要找到第一个包含那个键的键值对。在 Swift 3 中我们可以使用 func firstValue(matchingKey key: Key) -> Value? { let matchingKeyValuePair = values.lazy.filter({ $0.0 == key }).first //... } 一旦我们拿到表示键值对的元组,我们就可以直接调用 func firstValue(matchingKey key: Key) -> Value? { return values.lazy.filter({ $0.0 == key }).first?.1 } 这几乎是 还有几个有意思的事情还没有实现。首先,需要有惰性求值的
extension Dictionary: SequenceType { typealias Generator = IndexingGenerator<[(Key,Value)]> func generate() -> Dictionary.Generator { return storage.flatMap({ $0.values }).generate() } } 接下来是删除键。首先,从占位对象中删除: extension Placeholder { mutating func removeValue(key key: Key) { values = values.filter({ $0.0 != key }) } } 然后从字典中删除 extension Dictionary { mutating func remove(key key: Key) { let position = abs(key.hashValue) % storage.count storage[position].removeValue(key: key) } } 在设置新值时先调用 最后,我们来看看如何重新计算数组容量。当字典包含非常多对象时,它的实际存储结构需要调整自身大小,可以把“非常多对象”看成一个大于2/3或者3/4的负载系数(对象数量除以数组长度)。我选择 0.7。 extension Dictionary { private let maxLoadFactor = 0.7 private var size: Int { return storage.count } var count: Int { return storage.flatMap({ $0.values }).count } var currentLoadFactor: Double { return Double(count) / Double(size) } } ( mutating func resizeIfNeeded() { if currentLoadFactor > maxLoadFactor { //resize storage } } 这就是 Swift 的值语法非常奇怪的地方。 在 Swift 中我们不必如此。Swift 中的结构体对象赋值给一个变量会进行一次完整拷贝。 let oldDictionary = self //... 一旦我们拷贝完字典,我们可以重置 //... storage = Array<Placeholder<Key,Value>>(count: size*2,Value>()) //... 一旦设置完成,字典会变成空的,我们必须把 //... for (key,value) in oldDictionary { self[key] = value } 这是完整的 mutating func resizeIfNeeded() { if Double(count) / Double(size) > maxLoadFactor { let oldDictionary = self storage = Array<Placeholder<Key,Value>()) for (key,value) in oldDictionary { self[key] = value } } } 在 Swift 的结构体中, 两周前 我开玩笑说 Swift 是比 Objective-C ,Ruby 或者 Python 更动态的语言。因为你可以改变它的错误行为,但是这里有另一种情况,即你可以在 Swift 中修改一些在 Objective-C 中不能修改的东西: 包含排序,删除,调整数组大小的完整的实现可以在 gist 中找到。除了
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- Swift 成员变量的get/set
- 将 Flex 3 的应用程序迁移到 Flex 4 — 第 1 部分:将 Flex
- Flexigrid绑定数据后更改符合条件的行的样式
- c – random_shuffle不是真的随机
- NoSQL(一)概述
- ruby-on-rails – 如何使用nokogiri和rubyzip编辑docx
- Flex编译器参数中-swf-version与-target-player之关系
- postgresql导出数据表还原
- oracle – DBMS_DATA_MINING.CREATE_MODEL在11.2.0.1.0 64b
- ruby-on-rails – Solr Sunspot – 重新索引对象不会自动运