Swift4中的字典和集合在這些方面變得更好
寫在前面
在最新版本的Swift中丧鸯,dictionaries和sets新增了很多行為方法和初始化方法惊窖,讓一些常見的任務(wù)變得異常簡單突诬。諸如組合漫雷、過濾和transform值操作可以用一步完成颂龙,讓使用者可以寫出更高效和簡潔的代碼习蓬。
本篇博客將使用雜貨鋪中的商品作為例子演示這些新功能纽什。GroceryItem結(jié)構(gòu)體,由名字和部門組成躲叼,作為本例的數(shù)據(jù)類型稿湿。
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數(shù)組展示。
用Key值對原數(shù)組進(jìn)行分組
字典擁有了一個(gè)新的初始化函數(shù)押赊,可以將一系列值按照Key值進(jìn)行分組饺藤。下面展示使用該初始化方法根據(jù)GroceryItem的department進(jìn)行分組的一個(gè)小例子。
<div align=center>
</div>
在老版本的Swift中流礁,用戶可以使用如下的代碼完成上述任務(wù)涕俗。
// 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、手動(dòng)循環(huán)并且需要檢查departement是否已經(jīng)存在了神帅。
在Swift4中再姑,用戶可以使用Dictionary(grouping:by)方法,僅需一行代碼就可以達(dá)到上述效果找御。所要做的是傳入一個(gè)閉包元镀,該閉包返回?cái)?shù)組每一項(xiàng)項(xiàng)對應(yīng)的Key值即可。
// Swift 4.0
let groceriesByDepartment = Dictionary(grouping: groceries,
by: { item in item.department })
// groceriesByDepartment[.bakery] == [??, ??]
最終的字典groceriesByDepartment對每個(gè)department都有唯一入口霎桅,而且該入口對應(yīng)著GroceryItem相應(yīng)的name栖疑。例如,使用.bakery作為入口滔驶,將返回[??, ??]數(shù)組遇革。
獲得字典值的數(shù)量
使用新的mapValues(_:)方法,用戶可以方便的獲得每個(gè)入口對應(yīng)數(shù)組的長度揭糕。以上面例子中獲取的groceriesByDepartment字典為例:
let departmentCounts = groceriesByDepartment.mapValues { items in items.count }
// departmentCounts[.bakery] == 2
因?yàn)樽值溆邢嗤膋ey萝快,只是值不同,所以可以不需要重新計(jì)算哈希值著角,從而使得調(diào)用mapValues(_:)方法比從頭建立字典快很多揪漩。
從鍵值對建立字典
Swift4提供了兩種方法給用戶從鍵值對序列生成字典,一種方法允許key有重復(fù)吏口,另一種不允許奄容。
使用zip(::)函數(shù)可以將一些了鍵值組合起來。例如下面的代碼就創(chuàng)立了一系列(String,GroceryItem)元組锨侯。
let zippedNames = zip(groceries.map { $0.name }, groceries)
zippedNames的每一項(xiàng)都是一個(gè)元組(tuple)嫩海,第一項(xiàng)是("Apples", ??).因?yàn)閚ame值是唯一的,下面的方法就可以創(chuàng)建一個(gè)字典囚痴,也是我們上面提到的不允許key值重復(fù)的方法叁怪。
<div align=center>
</div>
var groceriesByName = Dictionary(uniqueKeysWithValues: zippedNames)
// groceriesByName["Apples"] == ??
// groceriesByName["Kumquats"] == nil
當(dāng)然,要使用該方法的前提是你可以確保key值是不重復(fù)的深滚。否則會(huì)引起runtime error奕谭。
如果key值可能會(huì)重復(fù)涣觉,使用另一個(gè)方法:Dictionary(_:uniquingKeysWith:)。這個(gè)方法需要傳入一個(gè)閉包來處理當(dāng)key重復(fù)時(shí)的操作血柳。閉包的第一個(gè)參數(shù)是key(鍵)對應(yīng)的old value(值)官册,而第二個(gè)對應(yīng)的是新值。用戶可以在閉包里寫相應(yīng)的邏輯难捌,比如新值替代老值膝宁,或者將新老值合并。
let pairs = [("dog", "??"), ("cat", "??"), ("dog", "??"), ("bunny", "??")]
let petmoji = Dictionary(pairs,
uniquingKeysWith: { (old, new) in new })
// petmoji["cat"] == "??"
// petmoji["dog"] == "??"
看上面的例子根吁,dog對應(yīng)了兩個(gè)值员淫。當(dāng)方法處理到("dog", "??”)時(shí),閉包的參數(shù)是 ("??”击敌, "??”)介返,而閉包的邏輯是返回第二個(gè)值,因此新值就代替了老值沃斤,最終的字典中圣蝎,dog對應(yīng)的值就是??。
篩選出特定的項(xiàng)
字典現(xiàn)在有了一個(gè)filter(_:)方法衡瓶,返回值是滿足條件的新字典(早期版本的swift返回的是一個(gè)數(shù)組)徘公。方法傳入的參數(shù)依然是一個(gè)閉包,如果某一項(xiàng)需要在返回值中出現(xiàn)鞍陨,閉包返回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決定某一項(xiàng)該不該出現(xiàn)在返回值字典中。
使用默認(rèn)值
字典現(xiàn)在提供了類似數(shù)組下標(biāo)來獲取和更新值键闺,下面的代碼定義了一個(gè)簡單的購物籃寿烟,key是商品,value是商品的數(shù)量辛燥。
// Begin with a single banana
var cart = [??: 1]
因?yàn)槟承﹌ey在字典中沒有對應(yīng)的值筛武,因此你用key去獲取值的時(shí)候,返回結(jié)果是optional的挎塌。
// One banana:
cart[??] // Optional(1)
// But no shrimp:
cart[??] // nil
可以使用??操作符將optinal值拆包為真實(shí)的數(shù)值徘六,現(xiàn)在swift4提供了另一種解決方案(設(shè)置默認(rèn)值),如果key對應(yīng)的值存在榴都,那么返回該值待锈,否則返回默認(rèn)值。如果key沒有對應(yīng)值嘴高,那么返回默認(rèn)值竿音。
// Still one banana:
cart[??, default: 0] // 1
// And zero shrimp:
cart[??, default: 0] // 0
甚至用下面的代碼簡化增加新item到購物車的過程和屎。
for item in [??, ??, ??] {
cart[item, default: 0] += 1
}
當(dāng)循環(huán)處理到??時(shí),檢索到當(dāng)前值春瞬,然后自增柴信,放回到原字典中。當(dāng)檢索到??時(shí)宽气,發(fā)現(xiàn)??現(xiàn)在并沒有對應(yīng)值随常,從而返回默認(rèn)值0,自增為1萄涯,存儲(chǔ)到字典中线罕,下次檢索的時(shí)候就變成了1.
合并兩個(gè)字典到一個(gè)字典中
將兩個(gè)字典合并也變得異常簡單。swift4提供了merge(_:uniquingKeysWith:)方法來處理合并操作窃判。和上面一樣钞楼,需要傳入一個(gè)閉包完成合并的邏輯,當(dāng)兩個(gè)字典擁有相同的key值時(shí)袄琳,由該閉包處理如何操作询件。
let otherCart = [??: 2, ??: 3]
cart.merge(otherCart, uniquingKeysWith: +)
// cart == [??: 5, ??: 3, ??: 1]
上面的代碼將相同key對應(yīng)的值相加作為新字典中的值。
如果不想原地合并唆樊,可以使用merging(_:uniquingKeysWith:)方法生成一個(gè)新字典宛琅。
And That’s Not All…
上面介紹的新特性并不是全部,限于篇幅逗旁,并沒有完全介紹全嘿辟。
和字典一樣,集合也擁有了新的filter(:) 方法片效,返回的也是集合红伦,而不是早起版本中的數(shù)組。字典和集合現(xiàn)在提供了暴漏現(xiàn)在capacity的方法:reserveCapacity(:)淀衣,有了該方法昙读,用戶可以看到并控制他們的內(nèi)部存儲(chǔ)。
Reference
本文譯自:https://swift.org/blog/dictionary-and-set-improvements/