map と filter を組み合わせる
map と filter を組み合わせる
このレッスンで分かること
mapもfilterも配列を一周するので、それぞれO(n)- 配列を扱うとき、私たちは「全部に同じ処理をしたい」「条件に合うものだけ取り出したい」という操作を頻繁に行います
map(配列, 関数)は 配列の各要素に関数を適用した新しい配列 を返します
map と filter を組み合わせる とは
配列に対して
mapとfilterを組み合わせ、偶数だけを 2 倍した結果を返す関数を実装する。関数型プログラミングの基礎を学ぶ。
配列を扱うとき、私たちは「全部に同じ処理をしたい」「条件に合うものだけ取り出したい」という操作を頻繁に行います。これらを for ループで書いてもよいのですが、関数型プログラミング の世界では map と filter という高階関数を使うことで、やりたいこと を宣言的に書けます。本章では関数型の基本ツールである map と filter の使い方と、それらを組み合わせる感覚を身につけます。
本レッスンでは「整数配列から偶数だけを取り出し、それぞれを 2 倍した結果を返す」関数を作ります。for で書けば数行ですが、filter と map で書くと処理の意図がコードに直接表れます。
関数型は 何をしたいか を書く。
forループは どう動かすか を書く。この差を意識するのが第一歩。
map とは
map(配列, 関数) は 配列の各要素に関数を適用した新しい配列 を返します。[1, 2, 3] を x => x * 2 で map すると [2, 4, 6] です。元の配列は変更されません。
Python
numbers = [1, 2, 3, 4]
doubled = list(map(lambda x: x * 2, numbers))
# doubled == [2, 4, 6, 8]JavaScript なら numbers.map(x => x * 2) です。Java なら Stream.of(...).map(x -> x * 2)、Go なら for で代用するのが一般的です。
filter とは
filter(配列, 述語) は 条件 (true / false を返す関数) を満たす要素だけを残した新しい配列 を返します。[1, 2, 3, 4] を x => x % 2 == 0 で filter すると [2, 4] です。
Python
numbers = [1, 2, 3, 4]
evens = list(filter(lambda x: x % 2 == 0, numbers))
# evens == [2, 4]組み合わせる
やりたいのは「偶数だけ → 2 倍」です。filter してから map を適用します。
図のポイント (テキスト併記)
- 図のように データがパイプを流れる イメージで考えるとわかりやすいです
図のように データがパイプを流れる イメージで考えるとわかりやすいです。途中の配列を変数に置く必要はありません。
Python での実装
Python
def mapFilter(nums):
return [n * 2 for n in nums if n % 2 == 0]Python では リスト内包表記 が読みやすくて速いです。map と filter をネストするより、内包表記が一般的です。
JavaScript での実装
JavaScript
function mapFilter(nums) {
return nums.filter(n => n % 2 === 0).map(n => n * 2);
}JavaScript は配列メソッドをチェーンするのが定番です。filter で偶数を残し、map で 2 倍します。中間配列が 1 つ作られますが、可読性のメリットが上回ります。
関数型のメリット
for 文よりコードが短く、変数の状態を変えない ので副作用が少ない、というのが関数型の利点です。さらに map filter reduce を組み合わせれば、複雑な処理を 小さな部品の合成 で表現できます。複雑な処理 を 小さな関数 に分解する習慣は、テストもしやすく、再利用もしやすいコードを生みます。
関数型の真髄は 小さな関数を組み合わせる こと。
mapとfilterはその最も基本的な部品。
計算量とパフォーマンス
map も filter も配列を一周するので、それぞれ O(n)。チェーンすれば O(2n) = O(n) で済みます。中間配列を確保するぶん、生の for よりわずかにメモリは多いですが、可読性のメリットがそれを上回ります。遅延評価 をサポートする Stream (Java) や Iterator (Python) を使うと、中間配列なしで実行できます。
元の配列を保護する
for ループだと、つい arr[i] = arr[i] * 2 のように 元の配列を直接書き換える コードを書いてしまいがちです。これは呼び出し側に予期しない副作用をもたらし、バグの温床になります。map filter は 必ず新しい配列を返す ので、呼び出し側の状態を壊しません。
Python
original = [1, 2, 3]
doubled = list(map(lambda x: x * 2, original))
# original は [1, 2, 3] のまま、doubled は [2, 4, 6]副作用なしの関数を 純粋関数 と呼ぶ。
mapfilterのコールバックは純粋関数として書くのが原則。
よくある間違い
1 つ目は map と filter の順序を間違える ことです。「偶数を 2 倍」なら filter -> map ですが、「2 倍した結果が偶数」を求めるなら map -> filter です。順序で意味が変わります。2 つ目は 元の配列を直接変更してしまう こと。map も filter も新しい配列を返すので、元の配列はそのまま残ります。3 つ目は 空配列を渡したとき の挙動を忘れること。map も filter も空配列を渡せば空配列を返します。例外にはなりません。
やってみよう
- 関数のロジックを「奇数だけ 3 倍」に変えてみる。
n % 2 === 1とn * 3に変えるだけ。 forループで書き直してみる。行数の差を実感する。- 関数を 2 つに分けて
filterとmapを別関数として書き、関数合成風にしてみる。
mapとfilterはあらゆる言語で使える基本操作。これを自然に使えるようになると、コードがぐっと読みやすくなる。
よくある質問
Q. map は for ループより速いですか?
A. CPython では大差ない場合が多いですが、map(int, strs) のように組み込み関数を渡すと内包表記より僅かに速いことがあります。JavaScript でも明確な差は出にくいため、可読性で選ぶのが基本で、配列が巨大な場合のみベンチマークしましょう。
Q. map と forEach の違いは?
A. map は新しい配列を返し、forEach は副作用を起こすだけで戻り値が undefined です。「変換後の配列を作る」なら map、「ログ出力や DOM 更新だけ」なら forEach を使うと意図が伝わります。途中で抜けたい場合は for...of + break を選びましょう。
Q. map の戻り値が思った形になりません
A. map は元と同じ長さの配列を返します。条件で要素数を変えたいなら filter を先にかけるか、flatMap で空配列 [] を返した分を平らにできます。インデックスが必要なら map((v, i) => ...) のように第 2 引数で受け取ってください。
次のレッスン
次は reduce で積を計算 で、配列の全要素を reduce (畳み込み) で 1 つの値にまとめる使い方を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- map と filter の合成 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. map と filter の合成 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- 偶数の要素だけを残し、それぞれを 2 倍した新しい配列を返す
- 元の配列 nums は変更しないこと
- 空配列が渡された場合は空配列を返す
入出力例
test-cases.txt
mapFilter([1,2,3,4,5,6]) → [4,8,12]
mapFilter([2,4,6]) → [4,8,12]
mapFilter([2,5]) → [4]
mapFilter([1,2,3,4]) → [4,8]
mapFilter([0,1,2]) → [0,4]
mapFilter([-2,-1,0,1,2]) → [-4,0,4]