JavaScript 逆引き集 | flatMap の変換+平坦化

JavaScript JavaScript
スポンサーリンク
スポンサーリンク

flatMap は「変換+1階層平坦化」を一度にやる魔法

arr.flatMap(x => x.items)

flatMap は、「map で変換してから flat で1階層平坦化する」処理を、1回でまとめてやってくれるメソッドです。 天才プログラマー視点で言うと、「配列の配列を扱うときに、map と flat を毎回つなげるのが面倒になったら flatMap に乗り換えろ」という感じの便利ツールです。

「各要素から配列を取り出して、それらを全部まとめて1本の配列にしたい」 まさにそのためのメソッドが flatMap です。

flatMap の基本構造と「変換+平坦化」が同時に起きる仕組み

まず、よくあるパターンから見てみます。

const arr = [
  { name: "Aki", items: ["apple", "banana"] },
  { name: "Mika", items: ["orange"] },
  { name: "Ken", items: ["grape", "melon"] }
];

const r = arr.flatMap(x => x.items);

console.log(r);
// ["apple", "banana", "orange", "grape", "melon"]
JavaScript

ここで起きていることを丁寧に分解します。

  1. flatMap は、まず map と同じように、各要素 x に対してコールバック x => x.items を実行します。 その結果は、[["apple", "banana"], ["orange"], ["grape", "melon"]] のような「配列の配列」になります。
  2. 次に、その「配列の配列」を flat() と同じように 1階層だけ平坦化します。 結果として、["apple", "banana", "orange", "grape", "melon"] という1本の配列になります。

つまり、「map で配列を取り出す → flat で1階層崩す」という2ステップを、 flatMap1ステップでやってくれるわけです。

map+flat と flatMap の違いを具体的に比較する

同じことを、mapflat を使って書くとこうなります。

const arr = [
  { name: "Aki", items: ["apple", "banana"] },
  { name: "Mika", items: ["orange"] },
  { name: "Ken", items: ["grape", "melon"] }
];

const nested = arr.map(x => x.items);
// [["apple", "banana"], ["orange"], ["grape", "melon"]]

const r = nested.flat();
// ["apple", "banana", "orange", "grape", "melon"]
JavaScript

これを flatMap で書くと、こうなります。

const r = arr.flatMap(x => x.items);
JavaScript

天才講師として一言でまとめるなら、 「map してから flat(1) するなら、flatMap にまとめた方がコードの意図がはっきりする」 ということです。

例題:カテゴリごとの商品リストを「全商品リスト」に変換する

const categories = [
  { category: "fruit", items: ["apple", "banana"] },
  { category: "drink", items: ["coffee", "tea"] },
  { category: "snack", items: ["cookie"] }
];

const allItems = categories.flatMap(c => c.items);

console.log(allItems);
// ["apple", "banana", "coffee", "tea", "cookie"]
JavaScript

ここでのイメージはこうです。

カテゴリごとに「商品配列」が入っている構造は、人間には分かりやすいけれど、 「全部の商品を1本の配列で扱いたい」ときには少し扱いづらい。

そこで flatMap を使うと、 「カテゴリごとの items を取り出して、それらを全部まとめて1本の配列にする」 という処理を、非常に自然な形で書けます。

flatMap のコールバックで「変換」も同時に行う

flatMap のコールバックは、単に配列を返すだけでなく、 その中で変換処理も一緒に書けます。

const categories = [
  { category: "fruit", items: ["apple", "banana"] },
  { category: "drink", items: ["coffee", "tea"] }
];

const labeledItems = categories.flatMap(c => {
  return c.items.map(item => `${c.category}:${item}`);
});

console.log(labeledItems);
// ["fruit:apple", "fruit:banana", "drink:coffee", "drink:tea"]
JavaScript

ここで起きていることはこうです。

  1. 各カテゴリ c に対して、c.items.map(...) で「カテゴリ名付きの文字列配列」を作る。
  2. その結果は「配列の配列」になる。
  3. flatMap がそれを1階層平坦化して、1本の配列にしてくれる。

「中でさらに map を使って変換し、その結果を flatMap に渡す」というパターンは、 プロの現場でもよく使われるテクニックです。

flatMap の「深さ」について少しだけ触れる

重要なポイントとして、flatMap が平坦化するのは 常に1階層だけです。 flat() のように「深さを指定する引数」はありません。

つまり、flatMap は本質的に 「map+flat(1)」のショートカットです。 ネストがもっと深い場合は、flatMap の結果に対してさらに flat(2) などを使う必要があります。

初心者のうちは、 「flatMap は“1階層だけ”平坦化する」と覚えておけば十分です。

よくあるミスと注意点を深掘りする

まず、flatMap のコールバックは 「配列を返すことが前提」です。 配列ではないものを返すと、意図した結果になりません。

const arr = [1, 2, 3];

const r = arr.flatMap(x => x * 2);
console.log(r); // [2, 4, 6](これは動くが、flat の意味はない)
JavaScript

この例では、flatMap は単なる map と同じ動きになっています。 本来の力を発揮するのは、「配列を返すとき」です。

const arr = [1, 2, 3];

const r = arr.flatMap(x => [x, x * 2]);
console.log(r); // [1, 2, 2, 4, 3, 6]
JavaScript

ここでは、

1 を [1, 2] に、 2 を [2, 4] に、 3 を [3, 6] に変換し、 それらを平坦化して 1 本の配列にしています。

「1つの要素から複数の要素を生み出し、それを全部並べたい」という場面で、flatMap は非常に強力です。

まとめ:flatMap は「map+flat(1)」を美しく書くためのプロ向けツール

flatMap を使いこなせると、配列のネストを扱うコードが一気に洗練されます。

配列の中に配列が入っている。 各要素から配列を取り出したい。 その配列たちを、1本の配列にまとめたい。

そんなときに、 arr.map(...).flat() ではなく、arr.flatMap(...) と書けるようになることが、 “配列処理を理解しているプログラマー”への階段の一段目です。

タイトルとURLをコピーしました