Swift4字典和集合的新特性
Swift4中的字典和集合在这些方面变得更好
写在前面
在最新版本的Swift中,dictionaries和sets新增了很多行为方法和初始化方法,让一些常见的任务变得异常简单。诸如组合、过滤和transform值操作可以用一步完成,让使用者可以写出更高效和简洁的代码。
本篇博客将使用杂货铺中的商品作为例子演示这些新功能。GroceryItem结构体,由名字和部门组成,作为本例的数据类型。
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进行分组的一个小例子。
<div align=center>
![](https://img.haomeiwen.com/i41854/105f88ede583e459.png)
</div>
在老版本的Swift中,用户可以使用如下的代码完成上述任务。
// 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值即可。
// Swift 4.0
let groceriesByDepartment = Dictionary(grouping: groceries,
by: { item in item.department })
// groceriesByDepartment[.bakery] == [🥐, 🍞]
最终的字典groceriesByDepartment对每个department都有唯一入口,而且该入口对应着GroceryItem相应的name。例如,使用.bakery作为入口,将返回[🥐, 🍞]数组。
获得字典值的数量
使用新的mapValues(_:)方法,用户可以方便的获得每个入口对应数组的长度。以上面例子中获取的groceriesByDepartment字典为例:
let departmentCounts = groceriesByDepartment.mapValues { items in items.count }
// departmentCounts[.bakery] == 2
因为字典有相同的key,只是值不同,所以可以不需要重新计算哈希值,从而使得调用mapValues(_:)方法比从头建立字典快很多。
从键值对建立字典
Swift4提供了两种方法给用户从键值对序列生成字典,一种方法允许key有重复,另一种不允许。
使用zip(::)函数可以将一些了键值组合起来。例如下面的代码就创立了一系列(String,GroceryItem)元组。
let zippedNames = zip(groceries.map { $0.name }, groceries)
zippedNames的每一项都是一个元组(tuple),第一项是("Apples", 🍎).因为name值是唯一的,下面的方法就可以创建一个字典,也是我们上面提到的不允许key值重复的方法。
<div align=center>
![](https://img.haomeiwen.com/i41854/0f968405e9c75f90.png)
</div>
var groceriesByName = Dictionary(uniqueKeysWithValues: zippedNames)
// groceriesByName["Apples"] == 🍎
// groceriesByName["Kumquats"] == nil
当然,要使用该方法的前提是你可以确保key值是不重复的。否则会引起runtime error。
如果key值可能会重复,使用另一个方法:Dictionary(_:uniquingKeysWith:)。这个方法需要传入一个闭包来处理当key重复时的操作。闭包的第一个参数是key(键)对应的old value(值),而第二个对应的是新值。用户可以在闭包里写相应的逻辑,比如新值替代老值,或者将新老值合并。
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。
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是商品的数量。
// Begin with a single banana
var cart = [🍌: 1]
因为某些key在字典中没有对应的值,因此你用key去获取值的时候,返回结果是optional的。
// One banana:
cart[🍌] // Optional(1)
// But no shrimp:
cart[🍤] // nil
可以使用??操作符将optinal值拆包为真实的数值,现在swift4提供了另一种解决方案(设置默认值),如果key对应的值存在,那么返回该值,否则返回默认值。如果key没有对应值,那么返回默认值。
// Still one banana:
cart[🍌, default: 0] // 1
// And zero shrimp:
cart[🍤, default: 0] // 0
甚至用下面的代码简化增加新item到购物车的过程。
for item in [🍌, 🍌, 🍞] {
cart[item, default: 0] += 1
}
当循环处理到🍌时,检索到当前值,然后自增,放回到原字典中。当检索到🍞时,发现🍞现在并没有对应值,从而返回默认值0,自增为1,存储到字典中,下次检索的时候就变成了1.
合并两个字典到一个字典中
将两个字典合并也变得异常简单。swift4提供了merge(_:uniquingKeysWith:)方法来处理合并操作。和上面一样,需要传入一个闭包完成合并的逻辑,当两个字典拥有相同的key值时,由该闭包处理如何操作。
![](https://img.haomeiwen.com/i41854/cbb5a86b9f74c795.png)
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/