Ruby, Scala, Swift, JavaScript での List や Array といったデータ型の map と flatMap について[1, 2, 3, [4, [5, [6]]]]
という何気ないデータを使って動きを見てみます。
map
mapの動作イメージは、箱から出した値を変換する関数に渡して変換し、その結果をまた箱に詰める感じだと思っています。
元のデータがリストの場合は、すべての要素が関数に渡され、それぞれ変換された値が入った新しい箱ができます。
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))
。
こうやって書くとmapと同じに見えますが、大きな違いは「変換!」の関数の戻り値で、mapの場合は値を返しますが、flatMapの場合は箱を返す必要があります。
せっかく合成した関数ですが、わかりやすいようにバラバラにするとこんな感じになります。
これだけだとflatMapの存在意義が感じられないのですが、箱で返すので実際にはいろいろ利用用途があります。値をいじるだけならmapでできますが、要素を増やしたり減らしたりするときはflatMapを使います。
例えば[値] => [値, -値]
という変換ができます。
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にはない。