Swift - Sequence to Dictionary

使用

通过传入一个不重复的 key 的元组,可以转为字典

1
2
3
let pairs = [("1", 1), ("2", 2), ("3", 3), ("1", 2)]
Dictionary(uniqueKeysWithValues: pairs)
// [("1", 1), ("2", 2), ("3", 3), ("4", 4)]

如果存在重复的 key,则会直接抛出异常 **Fatal error: Duplicate values for key:**

1
2
3
4
5
let pairs = [("1", 1), ("2", 2), ("3", 3), ("1", 4)]
let dict = Dictionary(pairs, uniquingKeysWith: { first, _ in first })
// ["2": 2, "1": 1, "3": 3]
let dict = Dictionary(pairs, uniquingKeysWith: { _, last in last })
// ["2": 2, "1": 4, "3": 3]

这一方法则可以处理存在相同 key 的情况,并且通过 uniquingKeysWith,确定 key 使用前面还是后面的值

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@inlinable // FIXME(sil-serialize-all)
public init<S: Sequence>(
uniqueKeysWithValues keysAndValues: S
) where S.Element == (Key, Value) {
if let d = keysAndValues as? Dictionary<Key, Value> {
self = d
} else {
self = Dictionary(minimumCapacity: keysAndValues.underestimatedCount)
// '_MergeError.keyCollision' is caught and handled with an appropriate
// error message one level down, inside _variantBuffer.merge(_:...).
try! _variantBuffer.merge(
keysAndValues,
uniquingKeysWith: { _, _ in throw _MergeError.keyCollision})
}
}

我们主要看 else 内容,可以看到,实际调用的 Dictionary(keysAndValues: Sequence, uniquingKeysWith: (_, _) throws -> _) 这一方法,并且 uniquingKeysWith 传入的是一个异常,如果执行就会挂掉

我们再来看之后的方法,里面并没有什么内容,大多数是申请内存相关的,最后是调用了 nativeMerge 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@inlinable // FIXME(sil-serialize-all)
public init<S: Sequence>(
_ keysAndValues: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows where S.Element == (Key, Value) {
self = Dictionary(minimumCapacity: keysAndValues.underestimatedCount)
try _variantBuffer.merge(keysAndValues, uniquingKeysWith: combine)
}

@inlinable // FIXME(sil-serialize-all)
internal mutating func merge<S: Sequence>(
_ keysAndValues: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows where S.Element == (Key, Value) {
ensureNativeBuffer()
try nativeMerge(keysAndValues, uniquingKeysWith: combine)
}

主要的内容就在这一方法中,其中涉及较多底层内存空间申请等相关逻辑,推荐 Swift标准库源码阅读笔记 - Dictionary 这篇文章了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@inlinable // FIXME(sil-serialize-all)
internal mutating func nativeMerge<S: Sequence>(
_ keysAndValues: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows where S.Element == (Key, Value) {
// 遍历传入的 keysAndValues
for (key, value) in keysAndValues {
// 查找当前的 dict 中是否存在 key,i 是内存空间,found 为是否存在
var (i, found) = asNative._find(key, startBucket: asNative._bucket(key))
// 已经存在 key
if found {
let bucketCount = asNative.bucketCount
_ = ensureUniqueNativeBuffer(withBucketCount: bucketCount)
do {
// 根据偏移量取出已经存在的值,然后和当前的 value 比较,执行传入的 combine,返回 newValue
let newValue = try combine(asNative.value(at: i.offset), value)
asNative.setKey(key, value: newValue, at: i.offset)
} catch _MergeError.keyCollision {
// 这就是不允许重复 key 所抛出的信息
fatalError("Duplicate values for key: '\(key)'")
}
// 不存在则直接存入
} else {
let minCapacity = asNative.count + 1
let (_, capacityChanged) = ensureUniqueNativeBuffer(
withCapacity: minCapacity)
if capacityChanged {
i = asNative._find(key, startBucket: asNative._bucket(key)).pos
}

asNative.initializeKey(key, value: value, at: i.offset)
asNative.count += 1
}
}
}

可以看到,通过传入的 combine,可以对 value 做多种处理,比如相加等等,对一个 Array,zip 后传入,可以做到计算 key 的次数

链接

Swift - Dictionary
Swift标准库源码阅读笔记 - Dictionary