Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
119
Help us understand the problem. What is going on with this article?
@hanawat

Swift4.0でDictionaryが大幅にパワーアップした

More than 1 year has passed since last update.

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に対しても行えるmergingmerge(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-inforEachでゴリゴリにやるしかありませんでした😭

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

おしまい👋

119
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
hanawat
iOS Engineer

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
119
Help us understand the problem. What is going on with this article?