何をしたいユーティリティか:「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"]
JavaScriptfilter と 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);
JavaScript3 つとも同じ結果になるはずです。
「どの書き方が自分にとって一番読みやすいか」を感じてみてください。
まとめ:map + filter 合成で「変換+絞り込み」を名前付きにする
業務コードでは、「絞り込んでから変換する」「変換してから要らないものを捨てる」処理が本当に多いです。
そのたびに filter(...).map(...) をベタ書きするのもアリですが、
mapFilter のように「変換しつつ、null/undefined は捨てる」ヘルパーを用意する。createFilterMap のように「filter → map のパイプ」を関数として作る。
こうしておくと、「何をしたい処理なのか」が関数名で伝わるようになります。
配列操作を“業務レベル”に持っていくコツは、よく出てくるパターンに名前をつけて、ユーティリティとして固定してしまうことです。
