Pythonのmap()、filter()、reduce()とその例

map()filter()reduce()` 関数は Python に関数型プログラミングの一端をもたらしました。

これら3つの関数は、リスト内包やループで置き換えることができる便利な関数ですが、いくつかの問題に対してよりエレガントで短絡的なアプローチを提供します。

続行する前に、前述のメソッドについて読む前に知っておくべきいくつかの事柄について説明します。

無名関数、無名メソッド、無名ラムダとは何ですか?

無名メソッドとは、名前を持たないメソッドのことで、 def method: を使ってメソッドを定義したときのように、識別子に束縛されていないメソッドのことです。

注意:多くの人は「無名関数」と「ラムダ関数」という言葉を同じように使っていますが、これらは同じではありません。

なぜなら、ほとんどのプログラミング言語ではラムダは匿名であり、すべての匿名関数はラムダであるからです。

これはPythonでも同じです。

したがって、この記事ではこの区別についてこれ以上触れません。

ラムダ関数(またはラムダ演算子)の構文とは何ですか?

lambda arguments: expression


ラムダは、名前のない1行のメソッドだと考えてください。

例えばPythonの他のメソッドと実質的に同じように動作します。

def add(x,y):
    return x + y


と翻訳することができます。

lambda x, y: x + y


ラムダは通常のPythonのメソッドとは異なり、1つの式しか持つことができず、いかなるステートメントも含むことができず、その戻り値の型は function オブジェクトであることが特徴です。

つまり、上のコードは x + y という値を返すのではなく、x + y を計算する関数を返すのです。

なぜラムダは map(), filter(), reduce() と関係があるのでしょうか?

これらの3つのメソッドでは、最初の引数として function オブジェクトを指定します。

この function オブジェクトは、あらかじめ定義されたメソッドの名前にすることができます (def add(x,y) のような)。

しかし、多くの場合、 map(), filter(), reduce() に渡される関数は一度しか使わないものなので、参照可能な関数を定義する意味がない場合が多いでしょう。

異なる map()/filter()/reduce() のニーズに対して新しい関数を定義することを避けるために、よりエレガントなソリューションとして、一度しか使わない、二度と使わない短い使い捨ての匿名関数 – ラムダを使用することができます。

map()関数

map()関数は、与えられたイテラブルのすべての項目を繰り返し処理し、それぞれの項目に対して引数として渡したfunction` を実行します。

構文は以下のとおりです。

map(function, iterable(s))


使いたい function を渡した後は、イテラブルオブジェクトを好きなだけ渡すことができます。

# Without using lambdas
def starts_with_A(s):
    return s[0] == "A"


fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(starts_with_A, fruit)


print(list(map_object))


このコードで得られるのは

[True, False, False, True, False]


見ての通り、リスト fruit の各要素に対して関数 starts_with_A() が評価され、新しいリストが作成されました。

この関数の結果は、リストに順次追加されていきます。

これと全く同じことを行うには、ラムダを使用するのがよりよい方法です。

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(lambda s: s[0] == "A", fruit)


print(list(map_object))


同じ出力が得られます。

[True, False, False, True, False]


注意: 各要素の値を表示するために map_object をリストにキャストしていることにお気づきでしょうか。

これは、リストに対して print() を呼び出すと、要素の実際の値が表示されるからです。

print()map_object` に対して呼び出すと、代わりにその値のメモリアドレスが表示されます。

map()関数はmap_object` 型を返しますが、これは反復可能であり、このように結果を表示することもできました。

for value in map_object:
    print(value)


もし map() 関数がリストを返すようにしたい場合は、関数を呼び出すときにキャストすればよいのです。

result_list = list(map(lambda s: s[0] == "A", fruit))


filter()関数

map()と同様に、filter()function` オブジェクトとイテラブルを受け取り、新しいリストを作成します。

その名前が示すように、 filter() はある条件を満たす要素だけを含む新しいリストを作成します。

つまり、渡した functionTrue を返していればよいのです。

そのシンタックスは

filter(function, iterable(s))


先ほどの例を使うと、新しいリストには starts_with_A() 関数が True を返す要素だけが含まれることがわかります。

# Without using lambdas
def starts_with_A(s):
    return s[0] == "A"


fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(starts_with_A, fruit)


print(list(filter_object))


このコードを実行すると、リストが短くなります。

['Apple', 'Apricot']


あるいは、ラムダを使って書き直します。

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(lambda s: s[0] == "A", fruit)


print(list(filter_object))


印刷しても同じ出力が得られます。

['Apple', 'Apricot']


reduce()関数

reduce()は、map()filter()とは異なる動作をします。

渡されたfunction` とイテラブルに基づいて新しいリストを返すわけではありません。

その代わりに、1つの値を返します。

また、Python 3 では reduce() はビルトイン関数ではなくなり、 functools モジュールで見つけることができるようになりました。

そのシンタックスは

reduce(function, sequence[, initial])


reduce()は、シーケンスの最初の2つのアイテムに対して渡されたfunctionを呼び出すことで動作します。

function が返す結果は、次の (この例では3番目の) 要素と一緒に function を呼び出す際に使用されます。

このプロセスは、シーケンスのすべての要素を通過するまで繰り返されます。

オプションの引数 initial がある場合は、この “ループ” の最初に、最初に呼び出した function の最初の要素が使用されます。

ある意味、 initial 要素は、与えられたときに最初の要素の前にある 0 番目の要素になります。

reduce()map()filter()` よりも少し理解しにくいので、ステップバイステップで例を見ていくことにしましょう。

    1. リスト [2, 4, 7, 3] から始めて、 add(x, y) 関数を reduce() に渡すときに、このリストと一緒に initial 値は指定しません。
    1. reduce()add(2, 4) を呼び出し、add()6 を返します。
    1. reduce()add(6, 7) を呼び出し (前の add() の呼び出しの結果とリストの次の要素をパラメータとして)、 add()13 を返します。
    1. reduce()add(13, 3) を呼び出し、 add()16 を返します。
  1. シーケンスにはもう要素が残っていないので、 reduce()16 を返します。

唯一の違いは、もし initial という値を与えていたら、1.5 のステップが追加されたことです。

ここで reduce()add(initial, 2) を呼び出し、その戻り値をステップ 2 で使用します。

それでは、 reduce() 関数を使ってみましょう。

from functools import reduce


def add(x, y):
    return x + y


list = [2, 4, 7, 3]
print(reduce(add, list))


このコードを実行すると、次のようになります。

16


ここでもラムダを使って書くことができます。

from functools import reduce


list = [2, 4, 7, 3]
print(reduce(lambda x, y: x + y, list))
print("With an initial value: " + str(reduce(lambda x, y: x + y, list, 10)))


そして、このコードは次のようになる。

16
With an initial value: 26


結論

前述したように、これらの関数は便利な関数です。

しかし、ラムダ式も含めて使いすぎには注意しましょう。

できる」からといって無理に使ってしまうと、読みにくいコードになってしまい、メンテナンスが大変になります。

関数やラムダ式を見た瞬間に、何が起こっているのかが絶対にわかる場合にのみ、これらのツールを使うようにしましょう。

もし、1つの map() 関数や1つのラムダ式に必要なロジックを収めるのに苦労したら、少し長い for-loop/defined メソッドを書いた方がずっと良いし、後で不必要に混乱することもありません。

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