reduce で畳み込む

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

reduce で畳み込む とは

functools.reduce でリストを 1 つの値に畳み込む方法を学びます。本レッスンでは、reduce で畳み込む の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

reduce で畳み込む

ここまで map (変換) と filter (絞り込み) を学びました。最後に紹介するのが reduce (畳み込み) です。reduce は「リスト全体を 1 つの値にまとめる」ための高階関数です。合計、最大値、文字列連結など、複数の値を 1 つに集約する処理に使えます。

map filter reduce は、関数型プログラミングの三種の神器です。3 つ揃うと、ほとんどのデータ整形が宣言的に書けるようになります。

import が必要

reducePython 2 までは組み込み関数でしたが、Python 3 からは functools モジュールに移されました。使うときは import が必要です。

Python

from functools import reduce result = reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5]) print(result)

結果は 15 です。reduce の引数は次の通りです。

  • 第 1 引数 2 引数を取る関数 (累積値, 現在の要素)
  • 第 2 引数 イテラブル
  • 第 3 引数 (任意) 初期値

動きを追ってみる

reduce(f, [1, 2, 3, 4]) の挙動をステップで追ってみます。

diagram (will load when visible)

左から順に 2 つずつ取り出して関数を適用し、その結果を次の入力にしていきます。これを 畳み込み (fold) と呼びます。

初期値を指定する

第 3 引数に初期値を渡せます。空リストでも安全に動くようになります。

Python

from functools import reduce result = reduce(lambda acc, x: acc + x, [], 0) print(result)

空リストでも 0 が返ります。初期値を指定しないと、空のときは TypeError になるので注意してください。

初期値は 単位元 を選ぶのがコツです。合計なら 0、積なら 1、文字列連結なら ""、リスト連結なら [] です。

実例

最大値を求めてみます。

Python

from functools import reduce result = reduce(lambda a, b: a if a > b else b, [3, 7, 2, 9, 4]) print(result)

reduce の中で三項演算子を使って、大きい方を残しています。結果は 9 です。組み込み max を使えば 1 行で済む処理ですが、reduce の柔軟さを示す良い例です。

文字列連結はこちら。

Python

from functools import reduce words = ["Hello", " ", "World", "!"] result = reduce(lambda acc, w: acc + w, words) print(result)

結果は Hello World! です。要素同士を順番に繋げていく操作です。

sum との比較

単純な合計なら、組み込みの sum を使う方が簡潔です。

Python

print(sum([1, 2, 3, 4, 5]))

reduce の真価は、もっと複雑な集約や、sum のような専用関数が用意されていない計算で発揮されます。たとえば 「リストの全要素の積」 は sum では作れないので、reduce の出番です。

Python

from functools import reduce product = reduce(lambda acc, x: acc * x, [1, 2, 3, 4]) print(product)

結果は 24 です。階乗計算もこの考え方で書けます。

よくある間違い

  • import を忘れて NameError: reduce になる
  • 空リストに初期値なしで reduce を呼んでしまう
  • 累積値と現在の要素の順番を逆にしてしまう

Python

from functools import reduce # 初期値なし + 空リスト = エラー reduce(lambda a, b: a + b, [])

これは TypeError です。空リストになり得るときは、必ず初期値を渡しましょう。

累積値は 第 1 引数 です。lambda acc, x: の順番を間違えると、文字列と数値で計算したときに直感に反する結果が出ることがあります。

map / filter / reduce のまとめ

ここまでで map filter reduce の 3 つを学びました。それぞれの役割は次のとおりです。

  • map 要素を 変換 する (リストの長さは変わらない)
  • filter 要素を 絞り込む (リストの長さは減る)
  • reduce 要素を 集約 する (リストが 1 つの値になる)

この 3 つを組み合わせれば、データ整形の大半は記述可能です。SQLSELECT WHERE GROUP BY に近い発想で書ける、と言っても良いでしょう。

やってみよう

それでは、整数のリスト arr を受け取って、全要素の合計を reduce で計算して返す関数 reduce_sum を書いてみましょう。from functools import reducesolution の中に書いてください。空リストでも 0 を返すように、初期値も渡すこと。テストでは reduce_sum([1, 2, 3]) のような形で呼び出されます。

よくある質問

Q. reduce はどんなときに使うべきですか?

A. 合計・最大値・グルーピングなど、配列を 1 つの値や別の構造にまとめたいときに有効です。単純な合計なら sum / Array.prototype.reduce((a,b)=>a+b,0) で OK ですが、複雑なロジックでは for ループの方が読みやすいこともあります。

Q. 初期値は省略しても良いですか?

A. 省略すると配列の先頭要素が初期値になり、空配列のときに TypeError になります。明示的に 0 や [] を渡しておくと安全で、型推論にも優しくなります。可読性と安全性の両面から初期値は付ける派が多数です。

Q. reduce でオブジェクトを作るには?

A. items.reduce((acc, x) => ({ ...acc, [x.id]: x }), {}) のように初期値に {} を渡し、各反復で展開構文を使います。要素数が多い場合は scatter で性能劣化するため、acc[x.id] = x; return acc; のように mutate する形も検討してください。

次のレッスン

次は 第6章クイズ — 関数と高階関数 で、functools を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. reduce の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. reduce とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. functools から reduce を import する
  2. reduce で全要素を加算する
  3. 初期値 0 を渡して空リストでも 0 を返せるようにする

入出力例

test-cases.txt

reduce_sum([1,2,3,4,5])15 reduce_sum([10])10 reduce_sum([])0 reduce_sum([10,-3,5])12 reduce_sum([0,0,0])0

ヒント

main.py
main.py
学習モード

メモ

reduce で畳み込む

⌘S で保存