Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
今回はPythonの便利なモジュールの1つ、functooolsモジュールが提供するreduceという機能の紹介です。
Contents
この公式ドキュメントでは、次のように解説が書かれています。
iterable の要素に対して、iterable を単一の値に短縮するような形で 2 つの引数をもつ function を左から右に累積的に適用します。
全く意味がわかりませんね。ですがあとの具体例で意味が明確に理解できるようになります。現段階では次の2点に注意を払っておいください。
これを意識しておいてもらえると、下記の具体例が理解しやすくなると思います。
さて、次に使い方を見ておきましょう。このような形となります。
functools.reduce(第1引数, 第2引数[, 第3引数])
第1引数 | 第2引数で設定するイテラブルオブジェクトの各要素に適用する関数 |
第2引数 | 第1引数で設定する関数が適用されるイテラブルオブジェクト(イテラブルオブジェクトの典型的な例の1つがリストオブジェクトです) |
第3引数(省略可能) | 初期化処理 |
なおイテラブル(オブジェクト)についての解説は「【初心者にもわかりやすく】Pythonのイテレータについて、その基本とfor文の仕組みを解説」という記事で解説していますので、ぜひご覧ください。このブログでも結構アクセス数の多い人気記事となっています。
では以下、具体例としてのコードを見ていきましょう。
さて、上でfunctools.reduceの引数について次のよう書きました。
これをわかりやすく理解するために次のようなコードを作りました。見てください。
def func(x, y):
"""
Args:
x : 1つ前の結果が入る
y: イテラブルの次の要素が入る
Returns:
引数x, yのタプルを返す
"""
return (x, y)
functools.reduce(func, [1])
functools.reduce(func, [1, 2])
functools.reduce(func, [1, 2, 3])
functools.reduce(func, [1, 2, 3, 4])
今回はfunctools.reduceについて、
第1引数 | funcという名前の関数(2つの引数x, y)を持つ |
第2引数 | リスト |
第3引数(省略可能) | なし |
このように設定しています。functools.resuceの第1引数となる関数は、2つの引数を持たなければなりません。したがって、上のコードでもx, y という2つの引数を設定します。
ではコードの15行目から1行ずつ見ていきましょう。
さてまず最初に15行目で、
functools.reduce(func, [1])
これを実行すると、
1
となります。これは、第2引数のイテラブルオブジェクト(今回はリストとして[1])がただ1つの要素しか持っていない場合に、
initializer が与えられておらず、 iterable が単一の要素しか持っていない場合、最初の要素が返されます。
公式ドキュメント:functools.reduce
という仕組みなっているからです。
次に16行目のコード、
functools.reduce(func, [1, 2])
これを実行すると、
(1, 2)
単純なタプルとなります。このタプルの内容は、
第1要素 | 1 (これはfunctools.reduce(func, [1])の結果) |
第2要素 | 2 |
となっています。
続いて17行目の、
functools.reduce(func, [1, 2, 3])
ですが、これを実行すると、
((1, 2), 3)
このように、「(◯, 3)」という2つの要素からなるタプルがまた出来上がりました。このタプルの要素については次のようになっています。
第1要素 | (1, 2)というタプル (これはfunctools.reduce(func, [1, 2])の結果) |
第2要素 | 3 |
ここまでの解説でもうおわかりかもしれませんが、最後に、
functools.reduce(func, [1, 2, 3, 4])
これを実行すると、
(((1, 2), 3), 4)
となり、これも結局は「(◯, 4)」という2つの要素からなるタプルにしかすぎず、
第1要素 | ((1, 2), 3)というタプル (これはfunctools.reduce(func, [1, 2, 3])の結果) |
第2要素 | 3 |
という構造になっています。すなわち、functools.reduceは、
イテラブルオブジェクト(今回はリスト)の第1要素に対して処理を実行
↓
その結果に対して、イテラブルオブジェクトの第2要素について処理を実行
↓
その結果に対して、イテラブルオブジェクトの第3要素について処理を実行
↓
その結果に対して、イテラブルオブジェクトの第4要素について処理を実行
↓
以下略
という仕組みになっています。1つ前の処理の結果を利用(前提とする)という点が「累積的」という意味ですね。
以上のことを理解していただければ、次のコード例2も理解が容易になるでしょう。
import functools
"""
functools.reduce(function, iterable[, initializer])
"""
# iterable が空の場合には標準の値になる
# オプションの initializer が存在する場合、まず最初にinitializerがイテラブルの先頭として処理される
functools.reduce(lambda x, y: x+y, [], 4) # ==> 4
# initializer が与えられておらず、かつ iterableが単一の要素しか持っていない場合、最初の要素が返される
functools.reduce(lambda x, y: x+y, [1]) # ==> 1
# 左引数 x は累計の値になり、右引数 y は iterable から取り出した更新値になります
functools.reduce(lambda x, y: x+y, [1, 2]) # ==> 3
functools.reduce(lambda x, y: x+y, [1, 2, 3]) # ==> 6
functools.reduce(lambda x, y: x+y, [1, 2, 3, 4]) # ==> 10
functools.reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) # ==> 15
functools.reduce(lambda x, y: x+y, [11, 12, 13, 14, 15]) # ==> 65
今回はfunctools.reduceの引数を次のようにしています。
第1引数 | ラムダ関数 |
第2引数 | リスト |
第3引数(省略可能) | 4 |
今回はfunctools.reduceの第3引数として初期化の処理を担当するもの(initializer:イニシャライザ)として、「4」を設定しています。このようにinitializerが設定されると、まずそれが第2引数のイテラブルの最初の要素として設定されます。結果、
functools.reduce(lambda x, y: x+y, [], 4)
を実行すると、
4
という結果になります。
それ以外の部分についてはコード内のコメントと、上の具体例1で解説した内容の通りとなります。
def gnrtr(*args):
for i in args:
yield i
functools.reduce(func, gnrtr("Apple", "Google", "MS"), 10)
結果は、
(((10, 'Apple'), 'Google'), 'MS')
となります。
ジェネレータ関数はジェネレータイテレータオブジェクトを作りますが、そもそもイテレータオブジェクトはイテラブルオブジェクトの一種ですから、イテレータオブジェクトの一種であるジェネレータイテレータオブジェクトもイテラブルオブジェクトとして使うことが可能です。
イテラブル⊃イテラブル∋ジェネレータイテレータ
という関係です。
これらイテレータやジェネレータについては以下の記事をご覧ください。