Help us understand the problem. What is going on with this article?

Ruby と Scala と Swift と JavaScript における map と flatMap

More than 3 years have passed since last update.

Ruby, Scala, Swift, JavaScript での List や Array といったデータ型の map と flatMap について[1, 2, 3, [4, [5, [6]]]]という何気ないデータを使って動きを見てみます。

map

mapの動作イメージは、箱から出した値を変換する関数に渡して変換し、その結果をまた箱に詰める感じだと思っています。

map.png

元のデータがリストの場合は、すべての要素が関数に渡され、それぞれ変換された値が入った新しい箱ができます。

Ruby

[1] pry(main)> [1, 2, 3, [4, [5, [6]]]].map {|x| p x; x }
1                              # 1つ目の要素
2                              # 2つめの要素
3                              # 3つめの要素
[4, [5, [6]]]                  # 4つめの要素
=> [1, 2, 3, [4, [5, [6]]]]    # 新しいリスト

Scala

scala> List(1, 2, 3, List(4, List(5, List(6)))).map { (x) => println(x); x }
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
List(4, List(5, List(6)))      # 4つ目の要素
res1: List[Any] = List(1, 2, 3, List(4, List(5, List(6))))    # 新しいリスト

Swift

println(([1, 2, 3, [4, [5, [6]]]] as [AnyObject]).map { i -> AnyObject in println(i); return i; })
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
(                              # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  | 4つ目の要素
            6                  #  |
        )                      #  |
    )                          #  |
)                              # ---
[1, 2, 3, (                    # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  | 新しいリスト
            6                  #  |
        )                      #  |
    )                          #  |
)]                             # ---

JavaScript

> [1, 2, 3, [4, [5, [6]]]].map(function(x){ console.log(x); return x; })
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
[ 4, [ 5, [ 6 ] ] ]            # 4つ目の要素
[ 1,                           # ---
  2,                           #  | 新しいリスト
  3,                           #  |
  [ 4, [ 5, [Object] ] ] ]     # ---

まとめ

どの言語も全部同じ挙動。

flatMap

flatMapはmapしてflattenする、ただのflattenとmapの合成関数だと思っています。
つまりこういうものなんじゃないかと→flatMap(f) = flatten(map(f))

flatmap1.png

こうやって書くとmapと同じに見えますが、大きな違いは「変換!」の関数の戻り値で、mapの場合は値を返しますが、flatMapの場合は箱を返す必要があります。
せっかく合成した関数ですが、わかりやすいようにバラバラにするとこんな感じになります。

flatmap2.png

これだけだとflatMapの存在意義が感じられないのですが、箱で返すので実際にはいろいろ利用用途があります。値をいじるだけならmapでできますが、要素を増やしたり減らしたりするときはflatMapを使います。
例えば[値] => [値, -値]という変換ができます。

flatmap3.png

Ruby

[1] pry(main)> [1, 2, 3, [4, [5, [6]]]].flat_map {|x| p x; [x, x] }
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
[4, [5, [6]]]                  # 4つ目の要素
=> [1, 1, 2, 2, 3, 3, [4, [5, [6]]], [4, [5, [6]]]]    # 新しいリスト

mapしてflattenすると、

[2] pry(main)> [1, 2, 3, [4, [5, [6]]]].map {|x| p x; [x, x] }.flatten
1
2
3
[4, [5, [6]]]
=> [1, 1, 2, 2, 3, 3, 4, 5, 6, 4, 5, 6]

あれ? 同じにならない。
実はflattenは引数を取るメソッドで、何段階flatにするのか指定できます。
指定しないと完全にflatにするみたいです。

[3] pry(main)> [1, 2, 3, [4, [5, [6]]]].map {|x| p x; [x, x] }.flatten(1)
1
2
3
[4, [5, [6]]]
=> [1, 1, 2, 2, 3, 3, [4, [5, [6]]], [4, [5, [6]]]]

1階層だけflatにしたら同じ結果になりました。

Scala

scala> List(1, 2, 3, List(4, List(5, List(6)))).flatMap { (x) => println(x); List(x, x) }
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
List(4, List(5, List(6)))      # 4つ目の要素
res2: List[Any] = List(1, 1, 2, 2, 3, 3, List(4, List(5, List(6))), List(4, List(5, List(6))))     # 新しいリスト

mapしてflattenすると、

scala> List(1, 2, 3, List(4, List(5, List(6)))).map{ (x) => println(x); List(x, x) }.flatten
1
2
3
List(4, List(5, List(6)))
res1: List[Any] = List(1, 1, 2, 2, 3, 3, List(4, List(5, List(6))), List(4, List(5, List(6))))

同じになりました。

Swift

println(([1, 2, 3, [4, [5, [6]]]] as [AnyObject]).flatMap { i -> [AnyObject] in println(i); return [i, i]; })
1                              # 1つ目の要素
2                              # 2つ目の要素
3                              # 3つ目の要素
(                              # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  | 4つ目の要素
            6                  #  |
        )                      #  |
    )                          #  |
)                              # ---
[1, 1, 2, 2, 3, 3, (           # ---
    4,                         #  |
        (                      #  |
        5,                     #  |
                (              #  |
            6                  #  |
        )                      #  |
    )                          #  |
), (                           #  | 新しいリスト
    4,                         #  |   
        (                      #  |
        5,                     #  |
                (              #  |
            6                  #  |
        )                      #  |
    )                          #  |
)]                             # ---

mapしてflattenしようとしたら、flattenはありませんでした。
しょうがないのでflattenの代わりにflatMapを使って、mapで作った箱をそのまま返す感じで。

println(([1, 2, 3, [4, [5, [6]]]] as [AnyObject]).map { i -> [AnyObject] in println(i); return [i, i]; }.flatMap { $0 })
1
2
3
(
    4,
        (
        5,
                (
            6
        )
    )
)
[1, 1, 2, 2, 3, 3, (
    4,
        (
        5,
                (
            6
        )
    )
), (
    4,
        (
        5,
                (
            6
        )
    )
)]

同じ結果になりました。

JavaScript

> [1, 2, 3, [4, [5, [6]]]].flatMap(function(x){ console.log(x); return [x, x]; })
TypeError: Object 1,2,3,4,5,6 has no method 'flatMap'
    at repl:1:27
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.emit (events.js:95:17)
    at Interface._onLine (readline.js:203:10)
    at Interface._line (readline.js:532:8)
    at Interface._ttyWrite (readline.js:761:14)
    at ReadStream.onkeypress (readline.js:100:10)
    at ReadStream.emit (events.js:98:17)
    at emitKey (readline.js:1096:12)
> [1, 2, 3, [4, [5, [6]]]].flatten
undefined    

そもそも無かった。

まとめ

  • 普通の使い方ならどの言語も全部同じ挙動。当たり前か、、、
  • JavaScriptにはない。
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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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