【JS】flatMapの使い方とreduceやfilterでの代用について
こんにちは!マスク焼けのひどいMizutani(@sirycity)です。顔がパンダさん。
今日は最近JavaScriptに追加された.flatMap()の使い方と、flatMapとreduce、場合によってはfilterで同じことできるよーって話をします。
flatMapとは
.map()とほぼ同じです。ただし、returnされるのが配列だった場合は展開されます。そんだけ。
例えば普通の計算だと一緒。数列を2倍にするやつとかこんな感じ。
;[1, 2, 3]
.map(n => n * 2)
[
// [2, 4, 6]
(1, 2, 3)
].flatMap(n => n * 2)
// [2, 4, 6]
次に2倍にした上で配列にしてみる。すると…
;[1, 2, 3]
.map(n => [n * 2])
[
// [[2], [4], [6]]
(1, 2, 3)
].flatMap(n => [n * 2])
// [2, 4, 6]
こんな感じ。mapはなんか配列がネストしちゃってるけど、flatMapは配列にしてreturnしたのに配列じゃなくなってる。
次に2倍にしたやつに犬🐶をくっつけて配列にして返してみる。なんで犬?
[1, 2, 3].flatMap(n => [n * 2, 🐶])
// [2, 🐶, 4, 🐶, 6, 🐶]
配列が綺麗に1列になった上に犬がしれっと配列に組み込まれました。
ここで大事なのは、配列の長さが元々の3から6に増えたこと。この配列の長さを増やせるというのがflatMapの長所!mapにはできません。
逆に今後は配列の長さを減らしてみます。2倍にした後に4だけ除外してみよう。
;[1, 2, 3].flatMap(n => (n * 2 === 4 ? [] : n * 2))
// [2, 6]
4だけ除外できました!そう、flatMapは空配列[]をreturnするとその要素を除外できるんですね!この配列の長さを減らせるのもmapにはできません。ただ、長さを減らすのはfilterでもできるので、何かをした上で長さを減らすみたいな時がflatMapの出番って考えると良いかと思います。
flatMapについてこれ以上くわしくはMDNで。
使用例
増やす方の基本形
flatMapは配列を増やすのが得意。増やすといえば、この間高輪ゲートウェイ駅が増えましたね。いやもう結構前だろ。
というわけで高輪ゲートウェイ駅を例に例えます。まずは高輪ゲートウェイができる前の山手線を切り出してみましょう。
const oldYamanote = ['東京', '有楽町', '新橋', '浜松町', '田町', '品川']
次にできた後のnew山手線。
const newYamanote = [
'東京',
'有楽町',
'新橋',
'浜松町',
'田町',
'高輪ゲートウェイ',
'品川',
]
flatMapを駆使して、old山手をnew山手にしてみよう!一番シンプルだとこんな感じ↓
const newYamanote = oldYamanote.flatMap(sta =>
sta === '田町' ? [sta, '高輪ゲートウェイ'] : sta,
)
「田町の次」じゃなくて「品川の前」みたいな書き方にするとこう。
const newYamanote = oldYamanote.flatMap(sta =>
sta === '品川' ? ['高輪ゲートウェイ', sta] : sta,
)
「東京駅から5番目」みたいな書き方にするとこう。
const newYamanote = oldYamanote.flatMap((sta, i) =>
i === 5 ? ['高輪ゲートウェイ', sta] : sta,
)
減らす方の基本形
減らすような処理も見ていこう。東京が壊滅したみたいな設定で、↓から東京を除外します。
const oldYamanote = ['東京', '有楽町', '新橋', '浜松町', '田町', '品川']
減らす方が簡単ですね。これでok。
const newYamanote = oldYamanote.flatMap(sta => (sta !== '東京都' ? sta : []))
ただ「東京を除外」くらいシンプルな条件ならfilterの方がいいわね。こう。
const newYamanote = oldYamanote.filter(sta => sta !== '東京都')
応用例
増やす方の応用例をもう少し。
高輪ゲートウェイだけじゃあれなので、ついでに東京と有楽町の間に鍛冶橋駐車場駅も作りましょう。夜行バス歓喜。
まずはベタ書き。でも条件が複雑化すると三項演算子ではきつい。
const newYamanote = oldYamanote.flatMap(sta =>
sta === '東京'
? [sta, '鍛冶橋駐車場']
: sta === '田町'
? [sta, '高輪ゲートウェイ']
: sta,
)
こんな感じに別で定義した方がいいかも。まあここはいくらでもやりようはあるね。
const staToInsert = {
東京: '鍛冶橋駐車場',
田町: '高輪ゲートウェイ',
}
const newYamanote = oldYamanote.flatMap(sta =>
staToInsert[sta] ? [sta, staToInsert[sta]] : sta,
)
reduceで代用
flatMapはわりと最近の機能だから環境によっては使えないかも…そんな時は.reduce()でも同じことができます。
(reduceで他メソッドを代用する系については昔の記事も見てね!)
reduceで追加する高輪ゲートウェイはこう!
const newYamanote = oldYamanote.reduce(
(pre, cur) => [...pre, cur, ...(cur === '田町' ? ['高輪ゲートウェイ'] : [])],
[],
)
めっちゃ複雑だけど挙動は一緒!というかreduceでやるのが複雑すぎたからflatMapが作られたまである。
↑のreduceのやつはざっくり解説するとこんなかんじ↓
const newYamanote = oldYamanote.reduce(
(pre, cur) => [
// 既にpushしてある要素全部の配列(pre)を一旦スプレッドでバラす
...pre,
// 今回追加するやつは無条件で追加
cur,
// 今回追加するのが田町なら
...(cur === '田町'
? // 高輪ゲートウェイを配列化してバラしたやつ(つまりそのまま)を追加
['高輪ゲートウェイ']
: // 田町じゃなければ空配列をバラしたやつ(無)を追加
[]),
// バラした諸々を全部配列でまとめてreturn
],
// 最初の要素は無し
[],
)
結論
flatMap、めっちゃ便利。どんどん使いましょう。
ここ大事なんだけど、名前どおり配列を展開する時に使うというよりは配列の長さを増やしたい時に使うって覚えた方がいいと思います。以上。