kitoko552.memo

kitoko552のメモ

まだfor文で消耗してるの? map, filter, flatMapを使えばfor文は要りません

要点

Swiftではmap, filter, flatMap, forEachを使えばfor文は必要ありません。

  • mapの例
let numbers: [Int] = [1, 2, 3]

// for文
var twice = [Int]()
for num in numbers {
  twice.append(num * 2)
}

// map
let twice = numbers.map { $0 * 2 }

// twice = [2, 4, 6]
  • filterの例
let numbers: [Int] = [1, 2, 3]

// for文
var odd = [Int]()
for num in numbers {
  if num % 2 != 0 {
    odd.append(num)
  }
}

// filter
let odd = numbers.filter { $0 % 2 != 0 }

// odd = [1, 3]
  • flatMapの例
struct Hoge {
  let num: Int
  var harf: Int? {
    if num % 2 != 0 {
      return nil
    }
    
    return num / 2
  }
}

let numbers: [Hoge] = [Hoge(num: 1), Hoge(num: 2), Hoge(num: 3), Hoge(num: 4)]

// for文
var res = [Int]()
for num in numbers {
  if let harf = num.harf {
    res.append(harf)
  }
}

// map
let res = numbers.map { $0.harf }.filter { $0 != nil }.map { $0! }

// flatMap
let res = numbers.flatMap { $0.harf }

// res = [1, 2]

Swiftはfunctionalに書こう。

Swiftでは一般的にfunctionalに書くのが好まれます。
理由は基本的に以下の3つだと思っています。

  • コード量が少なく書ける。
  • 変数を定義する際はvarを使わずletで定義できる。
  • なんかかっこいい。

これは後に出てくる例を見ればわかるでしょう。

基本はmap, filter, flatMap

Swiftをfunctionalに書く上で基本となるメソッドがmap, filter, flatMapです。
map, filter, flatMapを使えば基本的に事足ります。
これに加えてforEachを使えばfor文はもう必要ありません。

map

コレクションの要素を使って別のコレクションを得たいときはmapを使います。
for文で書くとvarでコレクションを定義した後にfor文内でappendするというかっこ悪い実装になってしまいます。
しかしmapを使えばこれが1行で書けるかつletでコレクションを定義できます。

let numbers: [Int] = [1, 2, 3]

// for文
var twice = [Int]()
for num in numbers {
  twice.append(num * 2)
}

// map
let twice = numbers.map { $0 * 2 }

// twice = [2, 4, 6]

filter

コレクションの要素をある条件でフィルタリングした結果のコレクションを取り出したいときはfilterを使います。
for文を用いるとvarでコレクションを定義した後にfor文内でif文で条件分岐後、appendするというこれまたかっこ悪い実装になってしまいます。
しかしfilterを使えばこれが1行で書けるかつletでコレクションを定義できます。

let numbers: [Int] = [1, 2, 3]

// for文
var odd = [Int]()
for num in numbers {
  if num % 2 != 0 {
    odd.append(num)
  }
}

// filter
let odd = numbers.filter { $0 % 2 != 0 }

// odd = [1, 3]

flatMap

mapを使って新しいコレクションを生成する際に、要素にnilが返ってくる場合があり、そのnilを除いたコレクションを得たいときに使えるのがflatMapです。
この場合はfor文でもmapでもどちらで書いてもダサい書き方になってしまいます。
for文の場合は、varでコレクションを定義した後にfor文内でif let文でnilチェック後appendするというかっこ悪い実装になってしまいます。
また、mapを使ってもmap, filter, mapと3つ繋げて書かなければいけないこれまたかっこ悪い実装になってしまいます。
これがflatMapを使うとスマートな書き方になります。
例に出てくる構造体の名前とかが適当であれですがまあこんな感じです。

struct Hoge {
  let num: Int
  var harf: Int? {
    if num % 2 != 0 {
      return nil
    }
    
    return num / 2
  }
}

let numbers: [Hoge] = [Hoge(num: 1), Hoge(num: 2), Hoge(num: 3), Hoge(num: 4)]

// for文
var res = [Int]()
for num in numbers {
  if let harf = num.harf {
    res.append(harf)
  }
}

// map
let res = numbers.map { $0.harf }.filter { $0 != nil }.map { $0! }

// flatMap
let res = numbers.flatMap { $0.harf }

// res = [1, 2]

flatMapは他にも、入れ子になった配列をフラットにすることができます。

let numbers: [[Int]] = [[1, 3], [1, 4], [3], []]
let res = numbers.flatMap { $0 }

// res = [1, 3, 1, 4, 3]

filter + mapはflatMapで書き換え可能

filterとmapがあれば大抵のことはできてしまうので、慣れないうちはfilter + mapを使ってしまいがちですが、filter + mapはflatMapで書き換え可能です。

// filter + map
let twiceOdd = array.filter { $0 % 2 != 0 }.map {
  return $0 * 2
}

// flatMap
let twiceOdd = array.flatMap { num -> Int? in
  if num % 2 == 0 {
    return nil
  }
    
  return num * 2
}

forEach

新しいコレクションを生成する必要はなく、単にコレクションのある要素を使って何か処理をしたいときはforEachを使います。
こういったときにmap等を使うとコンパイラから、新しい配列返すからそれ使えよ的な警告を出されてしまいます。
forEachについては以下の記事でも書いたので参考にしてください。

blog.kitoko552.com

簡単に書くとこんな感じに書けるよってことです。

// for文
for element in array {
  if element == 2 {
    // Do something
  }
}

// forEach
array.forEach {
  if $0 == 2 {
    // Do something
  }
}

これらを使えば

もうfor文は必要ありません。
Swiftでfor文を使うなどもはやダサいことなのです!

書き方について

map, filter, flatMapは上の例のような書き方でなくても書けます。
しかし僕はこの書き方が一番シンプルでわかりやすく、かっこいいと思っています。

おわり