Swift 集合 Dictionary

从 Swift 中 Dictionary 的声明,可知,其是一个结构体,且保存的是键值对集合,并且作为作为键的类型必须是可哈希的,这使其如同一个哈希表一样可以通过键来快速访问相对应的值。

public struct Dictionary<Key, Value> where Key : Hashable { ... }

这就意味着所有遵循 Hashable 协议的类型都可以作为 Dictionary 的键,或者说要作为 Dictionary 的键必须要遵循 Hashable 协议。

如此,Swift 中的基本类型都是可以作为键的,而如果想要将自己定义的类型作为键,则必须要遵循 Hashable 协议。

let dic = [1:"a",2:"b",3:"c"]

单单从声明上看,似乎 Dictionary 就是元素为键值对的 Array 集合。但是,实际上 Dictionary 同 Array 区别明显,如向 Dictionary 变量中添加键值对时,如果超出容量,那么容量会增倍,随着内容不断增加,如果缓存不足,已存在的键值对可能会被覆盖,且没有任何提示。

var dic: Dictionary<Int,Int> = [:]
for index in 0...10 {
    print("count: ",dic.count," capacity: ", dic.capacity)
    dic[index] = index
}

输出如下:

count:  0  capacity:  1
count:  1  capacity:  1
count:  2  capacity:  3
count:  3  capacity:  3
count:  4  capacity:  6
count:  5  capacity:  6
count:  6  capacity:  6
count:  7  capacity:  12
count:  8  capacity:  12
count:  9  capacity:  12
count:  10  capacity:  12

所以,如果知道需要保存的键值对的数量,最好在创建 Dictionary 变量时,指定其最小容量。

同 Array 可以转换为 NSArray 一样,Dictionary 相应的可以转换为 NSDictionary 类型,只是其中的键和值都应是类,或者是遵循了经 @objc 修饰的协议的类型。如果不满足这两个要求,那么相应元素的转换,会在其第一次被访问时进行。

同样的,NSDictionary 会和 Dictionary 共享一片内存中的内容,直到对内容进行了修改。

声明和使用

除了直接赋值进行声明外,还可以使用一个序列来初始化 Dictionary 变量。

var dic = Dictionary.init(uniqueKeysWithValues: zip([1,2,3,4],[5,6,7,8]))

这个方法要求序列中的键值对中的键必须是不重复的,不过,Dictionary 中也提供了一个含有闭包参数的方法来解决键重复的情况。

var dic2 = Dictionary.init(zip([1,1,2,2],[5,6,7,8]),uniquingKeysWith: { (value1,value2) in
	return value1 > value2 ? value1 : value2
})

输出如下:

[4: 8, 1: 5, 3: 7, 2: 6]
[1: 6, 2: 8]

不过,Dictionary 中还提供了一个非常方便的分类方法,可以将一个序列中的值进行分类,或者说键是通过值来确定的。

var dic3 = Dictionary.init(grouping: [1,2,3,4,5,6], by: { value -> String in
    if value > 3 {
        return "greater than 3"
    }else {
        return "less and equal 3"
    }
})
    
print(dic3)

如此,就可以对不同的值进行分类:

["greater than 3": [4, 5, 6], "less and equal 3": [1, 2, 3]]

访问 Dictionary 变量的值时,不仅仅可以使用键作为下标,还可以使用索引。为此,Dictionary 中定义了一个 Index 结构体,来表示其索引。

public struct Index {
}

这个结构体没有定义额外的属性或者方法,作为一种类型,方便使用,如下是通过键获取索引的方法,以及对下标定义的重写。

@inlinable public func index(forKey key: Key) -> Dictionary<Key, Value>.Index?

@inlinable public subscript(position: Dictionary<Key, Value>.Index) -> [Key : Value].Element { get }

不过,从定义看可以知道,这种方式是只读的,所以不能用该种方式修改 Dictionary 中的键值对。

Dictionary 中提供了很多关于索引的函数,灵活使用他们可以提高效率。

@inlinable public func firstIndex(where predicate: ((key: Key, value: Value)) throws -> Bool) rethrows -> Dictionary<Key, Value>.Index?

@inlinable public func formIndex(after i: inout Dictionary<Key, Value>.Index)

通常使用时,需要先获取符合指定条件的键值对的索引,可以使用上面第一个函数。

如果已经得到了一个索引,可以使用第二个函数,该函数会将传入的索引修改为其对应的键值对的下一个键值对的索引。

或者使用下面的函数,直接得到满足条件的键值对。

@inlinable public func first(where predicate: ((key: Key, value: Value)) throws -> Bool) rethrows -> (key: Key, value: Value)?

只是其返回值为元组类型的可选类型。

只是这种索引方式,虽然能够对 Dictionary 变量进行访问,却不能对其进行修改,不过 Dictionary 中也提供了相应的修改方法。

如下面的方法,就是通过一个闭包参数对 Dictionary 中的值进行修改,返回新的 Dictionary 变量。

@inlinable public func mapValues<T>(_ transform: (Value) throws -> T) rethrows -> [Key : T]

下面两个方法,还可以将一个 Dictionary 变量,或者键值对序列合并到当前变量中,并使用闭包参数处理重复的键。

@inlinable public mutating func merge(_ other: __owned [Key : Value], uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows

@inlinable public mutating func merge<S>(_ other: __owned S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)

此外,Dictionary 中还提供了两个属性,返回该类型的键的集合和值的集合。

@inlinable public var keys: Dictionary<Key, Value>.Keys { get }
@inlinable public var values: Dictionary<Key, Value>.Values

public struct Keys : Collection, Equatable, CustomStringConvertible, CustomDebugStringConvertible { ... }
public struct Values : MutableCollection, CustomStringConvertible, CustomDebugStringConvertible { ... }

两者都是结构体类型,并遵循了 Collection 协议。