198
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

mapとflatMapという便利メソッドを理解する

最近になってmapやflatMapの勉強をしたのでその辺りを書いてみます。

mapやflatMapなんですが配列にもOptionalにもあるしObjective-cにはないメソッドなので最初見た時は少し混乱しました。
それぞれ別物と捉えて1つ1つについて調べていったら分かりやすかったので、その視点で記事を書いていきます。

今回はmapは2種類、flatMapは3種類に分類しました。

map(配列用)

1つ目のmapは配列のメソッドです。(正確にはSequenceTypeやらCollectionTypeにあるメソッドなので配列以外でも使えますが)
配列のmapは配列の要素1つ1つに操作をした結果の配列を返します。

[1, 2, 3].map { $0 * 2 } // → [2, 4, 6]

["3", "2", "1"].map { Int($0) } // → [Optional(3), Optional(2), Optional(1)]

クラスの配列からID一覧を取得したりする時に便利でした。

class MyClass {
    var id: Int
    init(id: Int) {
        self.id = id
    }
}
let myClasses = [MyClass(id: 1), MyClass(id: 2), MyClass(id: 3)]
myClasses.map { $0.id } // → [1, 2, 3]

map(Optional用)

mapメソッドはOptionalにもあります。
中身がnilなら何もせず、中身がnil以外なら処理をするものです。

let value: Int? = 1
var result: Int? = value.map { $0 + 1 } // → Optional(2)
// ↑ と ↓ は同じ意味
result = value != nil ? value! + 1 : nil // → Optional(2)

便利ではあるのですが、自分はif letguardでnilを除外する事が多いので配列のmapに比べると利用頻度は低いです。

let optionalValue: Int? = 1
guard value = optionalValue else { return }

result = value + 1 // → 1

flatMap(2重の配列用)

次にflatMapについて書きます。
flatMapはmapのような動きをして、更に2重の配列を1重に直してくれます。

[[1, 2], [3]].flatMap { $0 } // → [1, 2, 3]

[[1, 2], [3]].flatMap { $0 + [1] } // → [1, 2, 1, 3, 1]

mapで同じ事をすると下の結果になります。

[[1, 2], [3]].map { $0 } // → [[1, 2], [3]]

[[1, 2], [3]].map { $0 + [1] } // → [[1, 2, 1], [3, 1]]

flatMap(Optional用)

flatMapメソッドはOptional型にも定義されています。
配列のflatMapはmapをした上で2重の配列を1重にしてくれましたが、OptionalのflatMapはmapをした上で結果をアンラップします。

let value: String? = "1"
// mapだと2重のOptionalになってしまう
value.map { Int($0) } // Optional(Optional(1))

// flatMapならただのOptionalになる
value.flatMap { Int($0) } // Optional(1)

ブロックの結果がnilの時はnilを返します。

let value: String? = ""
print(value.map { Int($0) }) // Optional(nil)

print(value.flatMap { Int($0) }) // nil

クロージャーがOptionalを返さない場合はどちらも同じ結果になります。

var value: Int? = nil
value.flatMap { $0 + 1 } // → nil
value = 1
value.flatMap { $0 + 1 } // → Optional(2)

value = nil
value.map { $0 + 1 } // → nil
value = 1
value.map { $0 + 1 } // → Optional(2)

flatMap(中がオプショナルな配列用)

最後にSwift2.0で追加されたflatMapです。
中身がOptionalの配列を中身をアンラップした配列にしてくれます。

["1", "2", "3"].map { Int($0) } // → [Optiona(1), Optiona(2), Optiona(3)]
["1", "2", "3"].flatMap { Int($0) } // → [1, 2, 3]

nilがある場合はnilを除外してくれます。

["1", "2", "string", "3"].map { Int($0) } // → [Optiona(1), Optiona(2), nil, Optiona(3)]
["1", "2", "string", "3"].flatMap { Int($0) } // → [1, 2, 3]

配列からnilを取り除く時も便利です。

[1, 2, nil, 3].flatMap { $0 } // → [1, 2, 3]

2重で中身がOptionalな配列にflatMapを使うとどうなるか

先ほどflatMapは2重の配列を1重の配列に直すものと中身のOptionalを取り出す2種類があると書きました。

[1, 2, 3, nil].flatMap { $0 } // → [1, 2, 3]
[[1], [2, 3]].flatMap { $0 } // → [1, 2, 3]

2重で中身がOptionalな配列にflatMapを使ったらどうなるか試してみました。

結果は下の通りで、Optionalをアンラップする方が優先されました。

[[1], [2, 3], nil].flatMap { $0 } // → [[1], [2, 3]]

もう一度flatMapを使えば2重の配列の中身を取り出せました。

[[1], [2, 3], nil].flatMap { $0 }.flatMap { $0 } // → [1, 2, 3]

mapとflatMapが同じ結果になるのはなぜか

mapとflatMapは場合によっては同じ値を返します。
なぜ同じ結果になるかを調べてみました。

[1, 2, 3].flatMap { $0 + 1 } // → [2, 3, 4]
[1, 2, 3].map { $0 + 1 } // → [2, 3, 4]

下の式を見ると分かるのですが、flatMapの戻り値がInt?になっています。
つまり下のようなケースでは中身がオプショナルな配列用のflatMapが使われます。
そして戻り値は通常のInt型がOptionalに変換されたものが返り、それがアンラップされるからmapと同様の結果になるようです。

[1, 2, 3].flatMap { (value: Int) -> Int? in
    return value + 1
}
[1, 2, 3].map { (value: Int) -> Int in
    return value + 1
}

参考URL

Swift 2.0の新しいflatMapが便利過ぎる

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
198
Help us understand the problem. What are the problem?