JavaScript Tips | 配列ユーティリティ:map + filter 合成

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「map + filter 合成」

「map + filter 合成」は、「変換(map)」と「絞り込み(filter)」を、きれいに組み合わせて使うためのパターンやユーティリティです。
業務だと、こんな処理がよく出てきます。

「ユーザー配列から、active なユーザーだけ取り出して、その名前だけの配列にしたい」
「商品配列から、在庫ありの商品だけ取り出して、その価格を税込みに変換したい」

これって、まさに「filter で絞ってから map で変換」か、「map で変換してから filter で要らないものを捨てる」処理です。
毎回ベタ書きしてもいいのですが、パターンとして整理しておくと、読みやすさと再利用性が一気に上がります。


前提確認:map と filter の役割

map は「形を変える」

map は、配列の各要素を別の値に変換して、新しい配列を作る関数です。

const numbers = [1, 2, 3];

const doubled = numbers.map((n) => n * 2);
// [2, 4, 6]
JavaScript

元の配列はそのまま、新しい配列が返ってきます。
「形を変える」「別の情報に写し取る」ときに使います。

filter は「要素を選ぶ」

filter は、条件を満たす要素だけを残して、新しい配列を作る関数です。

const numbers = [1, 2, 3, 4, 5];

const evens = numbers.filter((n) => n % 2 === 0);
// [2, 4]
JavaScript

「残すか捨てるか」を決めるのが filter です。


map と filter を素直に組み合わせる基本形

例題:active なユーザーの名前だけを取り出す

よくある処理を、まずは素直に書いてみます。

const users = [
  { id: 1, name: "A", active: true },
  { id: 2, name: "B", active: false },
  { id: 3, name: "C", active: true },
];

const activeNames = users
  .filter((u) => u.active)
  .map((u) => u.name);

// ["A", "C"]
JavaScript

「active で絞る」→「name に変換する」という流れです。
この書き方自体はとても良いです。
ただ、業務で何度も同じパターンが出てくるなら、“map + filter の合成ユーティリティ”として切り出しておくと便利です。


map + filter を 1 回の reduce にまとめる mapFilter

「変換しつつ、要らないものは捨てる」ヘルパー

次のようなユーティリティを考えてみます。

function mapFilter(array, mapper) {
  if (!Array.isArray(array)) {
    return [];
  }
  if (typeof mapper !== "function") {
    return array.slice();
  }

  return array.reduce((acc, item, index) => {
    const result = mapper(item, index);
    if (result !== undefined && result !== null) {
      acc.push(result);
    }
    return acc;
  }, []);
}
JavaScript

ここでの重要ポイントをかみ砕きます。

mapper は「変換関数」ですが、同時に「残すか捨てるか」も決めます。
mapper が undefined または null を返したときは「捨てる」、それ以外は「残す」と決めています。
内部では reduce を使って、「変換」と「フィルタ」を一気にやっています。

つまり、「map した結果が null/undefined のものは自動的に除外される map」です。

例題:active なユーザーだけ名前を返し、それ以外は捨てる

const users = [
  { id: 1, name: "A", active: true },
  { id: 2, name: "B", active: false },
  { id: 3, name: "C", active: true },
];

const activeNames = mapFilter(users, (u) => {
  if (!u.active) {
    return undefined; // 捨てる
  }
  return u.name;      // 残す
});

// ["A", "C"]
JavaScript

filter と map を別々に書く代わりに、
「残したいときだけ値を返す」「捨てたいときは undefined を返す」というスタイルで 1 回にまとめています。


map + filter 合成のメリットと注意点

メリット

処理の流れが「1 回のストーリー」になる。
「この関数は“変換しつつ、要らないものは捨てる”」と名前で表現できる。
reduce の中で push しているので、配列を 1 回だけ走査すればよい。

特に、「変換してみた結果、無効なものは捨てたい」というケースに相性がいいです。

例えば、「文字列を数値に変換して、NaN になったものは捨てる」など。

const values = ["1", "2", "x", "3"];

const numbers = mapFilter(values, (v) => {
  const n = Number(v);
  return Number.isNaN(n) ? undefined : n;
});

// [1, 2, 3]
JavaScript

注意点

「filter → map」と「mapFilter」は、どちらが正しいという話ではありません。
「処理の意図が読みやすいかどうか」で選ぶのが大事です。

「まず絞ってから変換する」と明確なときは、素直に filter(...).map(...) のほうが読みやすいことも多いです。
「変換してみないと捨てるかどうか決められない」ようなときは、mapFilter のほうが自然です。


map と filter の“合成”を関数として作る

filter → map のパイプを作る helper

もう一つの方向性として、「filter と map を組み合わせた“処理パイプ”を返す関数」を作る方法もあります。

function createFilterMap(filterFn, mapFn) {
  return function run(array) {
    if (!Array.isArray(array)) {
      return [];
    }
    return array.filter(filterFn).map(mapFn);
  };
}
JavaScript

使い方はこうです。

const isActive = (u) => u.active;
const toName   = (u) => u.name;

const activeNamesFn = createFilterMap(isActive, toName);

const users = [
  { id: 1, name: "A", active: true },
  { id: 2, name: "B", active: false },
  { id: 3, name: "C", active: true },
];

const activeNames = activeNamesFn(users);
// ["A", "C"]
JavaScript

「active で絞ってから name に変換する」という処理を、
activeNamesFn という 1 つの“処理パイプ”として再利用できるようになります。


手を動かして map + filter 合成の感覚をつかむ

次のコードをコンソールで実行して、挙動を自分の目で確認してみてください。

function mapFilter(array, mapper) {
  if (!Array.isArray(array)) return [];
  if (typeof mapper !== "function") return array.slice();
  return array.reduce((acc, item, index) => {
    const result = mapper(item, index);
    if (result !== undefined && result !== null) {
      acc.push(result);
    }
    return acc;
  }, []);
}

function createFilterMap(filterFn, mapFn) {
  return function run(array) {
    if (!Array.isArray(array)) return [];
    return array.filter(filterFn).map(mapFn);
  };
}

const users = [
  { id: 1, name: "A", active: true },
  { id: 2, name: "B", active: false },
  { id: 3, name: "C", active: true },
];

const activeNames1 = users
  .filter((u) => u.active)
  .map((u) => u.name);

const activeNames2 = mapFilter(users, (u) => (u.active ? u.name : undefined));

const isActive = (u) => u.active;
const toName   = (u) => u.name;
const activeNamesFn = createFilterMap(isActive, toName);
const activeNames3 = activeNamesFn(users);

console.log(activeNames1);
console.log(activeNames2);
console.log(activeNames3);
JavaScript

3 つとも同じ結果になるはずです。
「どの書き方が自分にとって一番読みやすいか」を感じてみてください。


まとめ:map + filter 合成で「変換+絞り込み」を名前付きにする

業務コードでは、「絞り込んでから変換する」「変換してから要らないものを捨てる」処理が本当に多いです。
そのたびに filter(...).map(...) をベタ書きするのもアリですが、

mapFilter のように「変換しつつ、null/undefined は捨てる」ヘルパーを用意する。
createFilterMap のように「filter → map のパイプ」を関数として作る。

こうしておくと、「何をしたい処理なのか」が関数名で伝わるようになります。
配列操作を“業務レベル”に持っていくコツは、よく出てくるパターンに名前をつけて、ユーティリティとして固定してしまうことです。

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