Swift 4の魅力の一面を3行で表す

  • 61
    いいね
  • 0
    コメント

先日 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
}

これは、 UserArrayteam ごとに集計するコードで、次の二つの新 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 だと長くなってしまうかと言うと、 Dictionarysubscript を使おうとした部分で、キーに対応した値が存在しないケースを別で扱わないといけないからです。 Dictionary を使った集計というのは(少なくとも僕は)よく書く処理であり、他の言語を書いている場合も含めてストレスフルなパターンの一つです。それを、 default 値を与えられるようにし、 += と組み合わせて使えるというのが素晴らしいところです。

また、 Sequence から何かを組み立てるときに reduce は便利ですが、 Swift 3 までは Dictionaryreduce で組み立てる良い方法がありませんでした。新しい reduce(into:_:)inout な引数を持つ関数を渡すというアイデアでその点を見事に解決しています。

それぞれの API の詳しい説明は次の投稿を御覧ください。

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)")
}