Zhihao's Studio.

Swift4字典和集合的新特性

Word count: 1,680 / Reading time: 7 min
2017/10/13 Share

Swift4中的字典和集合在这些方面变得更好

写在前面

在最新版本的Swift中,dictionaries和sets新增了很多行为方法和初始化方法,让一些常见的任务变得异常简单。诸如组合、过滤和transform值操作可以用一步完成,让使用者可以写出更高效和简洁的代码。

本篇博客将使用杂货铺中的商品作为例子演示这些新功能。GroceryItem结构体,由名字和部门组成,作为本例的数据类型。

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
struct GroceryItem: Hashable {
var name: String
var department: Department
enum Department {
case bakery, produce, seafood
}
static func ==(lhs: GroceryItem, rhs: GroceryItem) -> Bool {
return (lhs.name, lhs.department) == (rhs.name, rhs.department)
}
var hashValue: Int {
// Combine the hash values for the name and department
return name.hashValue << 2 | department.hashValue
}
}
// Create some groceries for our store:
let 🍎 = GroceryItem(name: "Apples", department: .produce)
let 🍌 = GroceryItem(name: "Bananas", department: .produce)
let 🥐 = GroceryItem(name: "Croissants", department: .bakery)
let 🐟 = GroceryItem(name: "Salmon", department: .seafood)
let 🍇 = GroceryItem(name: "Grapes", department: .produce)
let 🍞 = GroceryItem(name: "Bread", department: .bakery)
let 🍤 = GroceryItem(name: "Shrimp", department: .seafood)
let groceries = [🍎, 🍌, 🥐, 🐟, 🍇, 🍞, 🍤]

后面的例子将围绕着groceries数组展示。

用Key值对原数组进行分组

字典拥有了一个新的初始化函数,可以将一系列值按照Key值进行分组。下面展示使用该初始化方法根据GroceryItem的department进行分组的一个小例子。



在老版本的Swift中,用户可以使用如下的代码完成上述任务。

1
2
3
4
5
6
7
8
9
// Swift <= 3.1
var grouped: [GroceryItem.Department: [GroceryItem]] = [:]
for item in groceries {
if grouped[item.department] != nil {
grouped[item.department]!.append(item)
} else {
grouped[item.department] = [item]
}
}

这一过程需要使用type annotations、手动循环并且需要检查departement是否已经存在了。

在Swift4中,用户可以使用Dictionary(grouping:by)方法,仅需一行代码就可以达到上述效果。所要做的是传入一个闭包,该闭包返回数组每一项项对应的Key值即可。

1
2
3
4
// Swift 4.0
let groceriesByDepartment = Dictionary(grouping: groceries,
by: { item in item.department })
// groceriesByDepartment[.bakery] == [🥐, 🍞]

最终的字典groceriesByDepartment对每个department都有唯一入口,而且该入口对应着GroceryItem相应的name。例如,使用.bakery作为入口,将返回[🥐, 🍞]数组。

获得字典值的数量

使用新的mapValues(_:)方法,用户可以方便的获得每个入口对应数组的长度。以上面例子中获取的groceriesByDepartment字典为例:

1
2
let departmentCounts = groceriesByDepartment.mapValues { items in items.count }
// departmentCounts[.bakery] == 2

因为字典有相同的key,只是值不同,所以可以不需要重新计算哈希值,从而使得调用mapValues(_:)方法比从头建立字典快很多。

从键值对建立字典

Swift4提供了两种方法给用户从键值对序列生成字典,一种方法允许key有重复,另一种不允许。

使用zip(::)函数可以将一些了键值组合起来。例如下面的代码就创立了一系列(String,GroceryItem)元组。

1
let zippedNames = zip(groceries.map { $0.name }, groceries)

zippedNames的每一项都是一个元组(tuple),第一项是(“Apples”, 🍎).因为name值是唯一的,下面的方法就可以创建一个字典,也是我们上面提到的不允许key值重复的方法。



1
2
3
var groceriesByName = Dictionary(uniqueKeysWithValues: zippedNames)
// groceriesByName["Apples"] == 🍎
// groceriesByName["Kumquats"] == nil

当然,要使用该方法的前提是你可以确保key值是不重复的。否则会引起runtime error。

如果key值可能会重复,使用另一个方法:Dictionary(_:uniquingKeysWith:)。这个方法需要传入一个闭包来处理当key重复时的操作。闭包的第一个参数是key(键)对应的old value(值),而第二个对应的是新值。用户可以在闭包里写相应的逻辑,比如新值替代老值,或者将新老值合并。

1
2
3
4
5
let pairs = [("dog", "🐕"), ("cat", "🐱"), ("dog", "🐶"), ("bunny", "🐰")]
let petmoji = Dictionary(pairs,
uniquingKeysWith: { (old, new) in new })
// petmoji["cat"] == "🐱"
// petmoji["dog"] == "🐶"

看上面的例子,dog对应了两个值。当方法处理到(“dog”, “🐶”)时,闭包的参数是 (“🐕”, “🐶”),而闭包的逻辑是返回第二个值,因此新值就代替了老值,最终的字典中,dog对应的值就是🐶。

筛选出特定的项

字典现在有了一个filter(_:)方法,返回值是满足条件的新字典(早期版本的swift返回的是一个数组)。方法传入的参数依然是一个闭包,如果某一项需要在返回值中出现,闭包返回true,否则返回false。

1
2
3
4
5
6
7
func isOutOfStock(_ item: GroceryItem) -> Bool {
// Looks up `item` in inventory
}
let outOfStock = groceriesByName.filter { (_, item) in isOutOfStock(item) }
// outOfStock["Croissants"] == 🥐
// outOfStock["Apples"] == nil

上例中,isOutOfStock决定某一项该不该出现在返回值字典中。

使用默认值

字典现在提供了类似数组下标来获取和更新值,下面的代码定义了一个简单的购物篮,key是商品,value是商品的数量。

1
2
// Begin with a single banana
var cart = [🍌: 1]

因为某些key在字典中没有对应的值,因此你用key去获取值的时候,返回结果是optional的。

1
2
3
4
// One banana:
cart[🍌] // Optional(1)
// But no shrimp:
cart[🍤] // nil

可以使用??操作符将optinal值拆包为真实的数值,现在swift4提供了另一种解决方案(设置默认值),如果key对应的值存在,那么返回该值,否则返回默认值。如果key没有对应值,那么返回默认值。

1
2
3
4
// Still one banana:
cart[🍌, default: 0] // 1
// And zero shrimp:
cart[🍤, default: 0] // 0

甚至用下面的代码简化增加新item到购物车的过程。

1
2
3
for item in [🍌, 🍌, 🍞] {
cart[item, default: 0] += 1
}

当循环处理到🍌时,检索到当前值,然后自增,放回到原字典中。当检索到🍞时,发现🍞现在并没有对应值,从而返回默认值0,自增为1,存储到字典中,下次检索的时候就变成了1.

合并两个字典到一个字典中

将两个字典合并也变得异常简单。swift4提供了merge(_:uniquingKeysWith:)方法来处理合并操作。和上面一样,需要传入一个闭包完成合并的逻辑,当两个字典拥有相同的key值时,由该闭包处理如何操作。

1
2
3
let otherCart = [🍌: 2, 🍇: 3]
cart.merge(otherCart, uniquingKeysWith: +)
// cart == [🍌: 5, 🍇: 3, 🍞: 1]

上面的代码将相同key对应的值相加作为新字典中的值。

如果不想原地合并,可以使用merging(_:uniquingKeysWith:)方法生成一个新字典。

And That’s Not All…

上面介绍的新特性并不是全部,限于篇幅,并没有完全介绍全。

和字典一样,集合也拥有了新的filter(_:) 方法,返回的也是集合,而不是早起版本中的数组。字典和集合现在提供了暴漏现在capacity的方法:reserveCapacity(_:),有了该方法,用户可以看到并控制他们的内部存储。

Reference

本文译自:https://swift.org/blog/dictionary-and-set-improvements/

CATALOG
  1. 1. Swift4中的字典和集合在这些方面变得更好
    1. 1.1. 写在前面
    2. 1.2. 用Key值对原数组进行分组
    3. 1.3. 获得字典值的数量
    4. 1.4. 从键值对建立字典
    5. 1.5. 筛选出特定的项
    6. 1.6. 使用默认值
    7. 1.7. 合并两个字典到一个字典中
    8. 1.8. And That’s Not All…
  2. 2. Reference