先日 Swift 4 がリリースされました。みんな注目しているのは Codable
など劇的にコーディングが楽になる新機能だと思いますが、ちょっとした便利な小技もあります。
そんな、 Swift 4 の小技の魅力の一面を 3 行にぎゅっと詰め込んだコードを思い付いたので紹介します。
// User の Array から、 team ごとの人数を集計する
let teamToCount: [String: Int] = users.reduce(into: [:]) { teamToCount, user in
teamToCount[user.team, default: 0] += 1
}
これは、 User
の Array
を team
ごとに集計するコードで、次の二つの新 API を使っています。
Swift 3だと
もし、 Swift 3 で同じことを書こうとすると次のようなコードになるでしょう。
// Swift 3 の場合
var teamToCount: [String: Int] = [:]
for user in users {
let team = user.team
if let count = teamToCount[team] {
teamToCount[team] = count + 1
} else {
teamToCount[team] = 1
}
}
長いですね・・・。しかも、 teamToCount
が無駄に var
になってしまいました。
ちょっとした解説
どうして Swift 3 だと長くなってしまうかと言うと、 Dictionary
の subscript
を使おうとした部分で、キーに対応した値が存在しないケースを別で扱わないといけないからです。 Dictionary
を使った集計というのは(少なくとも僕は)よく書く処理であり、他の言語を書いている場合も含めてストレスフルなパターンの一つです。それを、 default
値を与えられるようにし、 +=
と組み合わせて使えるというのが素晴らしいところです。
また、 Sequence
から何かを組み立てるときに reduce
は便利ですが、 Swift 3 までは Dictionary
を reduce
で組み立てる良い方法がありませんでした。新しい reduce(into:_:)
は inout
な引数を持つ関数を渡すというアイデアでその点を見事に解決しています。
それぞれの API の詳しい説明は次の投稿を御覧ください。
-
Dictionary
のsubscript(_:default:)
: Swift4.0でDictionaryが大幅にパワーアップした by @hanawat -
Sequence
のreduce(into:_:)
: Swift 4の新しいreduceが素晴らしいので紹介する by @koher
group(by:)
的なメソッドがあればもっと簡単なんじゃないかという考え方もありますが、よりプリミティブで汎用性が高く、使い勝手の良い道具が用意されて、それを組み合わせてこれまで面倒だったケースに対応できたというところが僕が感動した点です。
動作するコード全体
検証用に書いた動作するコードの全体です。そのまま実行できます。
// 型の準備
struct User {
var team: String
}
// 値の準備
let users = [
User(team: "A"),
User(team: "B"),
User(team: "B"),
User(team: "A"),
User(team: "C"),
User(team: "A"),
User(team: "C"),
User(team: "B"),
User(team: "D"),
User(team: "A"),
]
// 集計
let teamToCount: [String: Int] = users.reduce(into: [:]) { teamToCount, user in
teamToCount[user.team, default: 0] += 1
}
// 出力
for (team, count) in (teamToCount.sorted { $0.key < $1.key }) {
print("\(team): \(count)")
}