Swift4.0でDictionaryがかなり使いやすくなったのでざっくり紹介したいと思います。
主にProposalsの内容なので詳しくはそちらを読んでください🙇
Sequenceタイプから初期化できる
init(uniqueKeysWithValues:)
Key-ValueのペアになったSequenceタイプからDictionaryを生成できるようになりました👏
let touples = [("Apple", 1), ("Orange", 2), ("Grape", 3)]
// ["Grape": 3, "Orange": 2, "Apple": 1]
let dictionary = Dictionary(uniqueKeysWithValues: touples)
上記のようにタプルだと要素が勝手に増やせてしまいKey-Valueの構成にならないので、
Swift3で入ったzip
を使うとペアの値という縛りができます。
let names = ["Apple", "Orange", "Grape"]
let count = [1, 2, 3]
let ziped = zip(names, count)
// ["Grape": 3, "Orange": 2, "Apple": 1]
let dictionary = Dictionary(uniqueKeysWithValues: ziped)
また、重複したKeyを含めた場合は実行時エラーになるので注意です⚠️
let names = ["Apple", "Apple", "Grape"]
let count = [1, 2, 3]
let ziped = zip(names, count)
// Execution was interrupted, reason: EXC_BAD_INSTRUCTION
let dictionary = Dictionary(uniqueKeysWithValues: ziped)
重複したKey(Sequenceタイプ)に対して処理できる
init(_:uniquingKeysWith:)
上記のように重複したKeyがあった場合に何かしらの処理をしてそのKeyのValueとして初期化できます。
let names = ["Apple", "Apple", "Grape"]
let count = [1, 2, 3]
let ziped = zip(names, count)
// ["Grape": 3, "Apple": 3]
let merged = Dictionary(ziped, uniquingKeysWith: +)
merging(_:uniquingKeysWith:)
初期化だけでなく既にあるDictionaryに対しても行えるmerging
とmerge
(mutating)も追加されています🙆
let dictionary = ["Grape": 3, "Orange": 2, "Apple": 1]
// ["Grape": 6, "Orange": 2, "Apple": 4]
let merged = dictionary.merging(ziped, uniquingKeysWith: +)
FilterやMapの扱いが楽になった
filter(_:)
Collectionタイプのfilter
がDictionaryのメソッドとして実装されました。
今まではタプルの配列が返ってきたのですが、ちゃんとDictionaryで返ってきます👏
let dictionary = ["Grape": 3, "Orange": 2, "Apple": 1]
// Swift3: [(key: "Grape", value: 3), (key: "Orange", value: 2)]
// Swift4: ["Grape": 3, "Orange": 2]
let filterd = dictionary.filter { $0.value > 1 }
mapValues(_:)
DictionaryのValueに何かしらの処理を加えて新たなDictionaryを生成したいことって結構あると思います。
今までは下記のようにfor-in
やforEach
でゴリゴリにやるしかありませんでした😭
let dictionary = ["Grape": 3, "Orange": 2, "Apple": 1]
var mapped = [String: Int]()
dictionary.forEach { touple in
mapped[touple.key] = touple.value * 2
}
// ["Grape": 6, "Orange": 4, "Apple": 2]
mapped
上記を解決するためにmapValues
というメソッドが追加されました👏
これはDictionaryのValueだけをクロージャー内で受け取り、新しいValueが返り値になっています。
なんでKeyはないんだよって思うかもしれませんが、取り出しにくくなるだけで重複させる可能性もあるのでいらないですね。
let dictionary = ["Grape": 3, "Orange": 2, "Apple": 1]
// ["Grape": 6, "Orange": 4, "Apple": 2]
let mapped = dictionary.mapValues { $0 * 2 }
ValueをNon-Optionalに取り出せるようになった
subscript(_:default:)
DictionaryにKeyが存在しないValueを取り出した場合はnil
になるのでNil Coalescing Operator(??
)を使っていたと思います。
Swift4ではKeyと一緒にValueにデフォルト値を指定できるようになりました👏
let dictionary = ["Grape": 3, "Orange": 2, "Apple": 1]
// 4
let potato = dictionary["Potato", default: 4]
ただ上記ような例だとそこまでありがたみもなくNil Coalescing Operatorの方がいいと思います。
しかし+=
などでValueの値を取り出してすぐにいじる場合はとても重宝すると思います。今までは…
let names = ["Apple", "Orange", "Apple", "Grape"]
var dictionary = [String: Int]()
names.forEach { name in
// Binary operator '+=' cannot be applied to operands of type 'Int?' and 'Int'
// dictionary[name] += 1
let value = dictionary[name] ?? 0
dictionary[name] = value + 1
}
// ["Grape": 1, "Orange": 1, "Apple": 2]
dictionary
上記のようにValueを一度Nil Coalescing Operatorで取り出してから処理しなくてはいけませんでした。
しかしデフォルト値を使えばこんなにスッキリ書けます👏
let names = ["Apple", "Orange", "Apple", "Grape"]
var dictionary = [String: Int]()
names.forEach { dictionary[$0, default: 0] += 1 }
// ["Grape": 1, "Orange": 1, "Apple": 2]
dictionary
GroupingしたDictionaryを簡単に生成できる
init(grouping:by:)
配列などのSequenceタイプから、クロージャー内で返した値をKeyにしたDictionaryを生成できます。今までは…
let names = ["Apple", "Grape", "Apricot", "Guava"]
var initialGroup = [Character: [String]]()
names.sorted().forEach { name in
let character = name.characters.first ?? "#"
var names = initialGroup[character] ?? []
names.append(name)
initialGroup[character] = names
}
// ["G": ["Grape", "Guava"], "A": ["Apple", "Apricot"]]
initialGroup
上記のようにとても面倒だったのですが、たった数行で頭文字や文字数ごとに分けるなどができてかなり便利です👏
let names = ["Apple", "Grape", "Apricot", "Guava"]
// ["G": ["Grape", "Guava"], "A": ["Apple", "Apricot"]]
let initialGroup = Dictionary(grouping: names) { $0.first ?? "#" }
// [5: ["Apple", "Grape", "Guava"], 7: ["Apricot"]]
let countGroup = Dictionary(grouping: names) { $0.count }
容量の指定ができる
reserveCapacity(_:)
Dictionaryで格納するデータ量が決まっている場合などに容量を指定できるようになりました。
reserveCapacity
を使えば指定した容量(Backing Strageを含む: 1,3,6,12..
)が確保されるのでパフォーマンスアップになります👏
var dictionary = [String: Int]()
print(dictionary.capacity) // 0
dictionary.reserveCapacity(4)
print(dictionary.capacity) // 6
dictionary.reserveCapacity(7)
print(dictionary.capacity) // 12
おしまい👋