内包表記/ジェネレータ式でreduceする

reduceと比較するにあたって、そもそも同等の処理をどうやって内包表記やジェネレータ式で書くのか、という疑問があるかもしれませんので、まずはそこから紹介します。

sumと組み合わせる

reduceの使い方として一番多いのは、シーケンスの各要素に対して何らかの計算を適用し、その結果を合算する、というものだと思います。そのような場合に限られますが、一旦計算結果をiterableで返し、最後に組み込み関数のsumで合算する、という方法が使えます。

例として、1から10までの数列について、それぞれの平方の総和を出力する処理を書いてみましょう。

sequence = range(1, 11)
sum_square = sum(x ** 2 for x in sequence)
print(sum_square)

内包表記ではなくジェネレータ式を使うのがポイントです。内包表記では平方した数列を一旦リストなどの実体に格納してしまうので、余分にメモリを消費してしまいます。

代入式(セイウチ演算子)を使う

最初に書いてしまいますが、この方法はおすすめしません。あくまで言語仕様上はこういう書き方もできなくはないというだけで、実際は素直に**reduce****for**ループを使ったほうが良いです。

Python3.8以降であれば、新しい機能である代入式を活用することができます。代入式とは簡単に言うと、代入を行うと同時にその値を返す、という構文で、:=演算子(縦にするとセイウチの目と牙に見えるのでセイウチ演算子とも呼ばれるとか)を用います。

実際に例を挙げると、こんな感じです。意味のないコードですが…。

a = 0

def dummy():
    global a
    return (a := 1)

b = dummy()

これを実行すると、abともに1が代入されます。普通の代入演算子=がPythonの:=

さて、この:=演算子と内包表記/ジェネレータ式を使ってどうreduce相当の処理を実現するかというと、こうなります。

今度は、1から10までの数列について、それぞれの平方の総乗を出力します。この方法なら総和でない集計も可能です。

import collections

sequence = range(1, 11)
product_square = sequence[0] ** 2
agg = (product_square := product_square * x ** 2 for x in sequence[1:])
collections.deque(agg, maxlen=1)
print(product_square)

まずは要素の平方値の累積を逐次product_squareに代入しつつ、その2番目からの数列をシーケンス(4,36,576,...)として生成しています。シーケンスの最後の要素と、シーケンスが最後に達したときのproduct_squareがともに求めたい値になります。

ジェネレータ式のため遅延評価されますので、シーケンスを最後まで回すためにcollections.dequeを実行しています。なぜこんなやり方をしているかというと、stackoverflow.comで、シーケンスを最後まで回すための最速の方法として紹介されていたからです。

#python #developers.io

Pythonのreduceと内包表記/ジェネレータ式を比較してみた
1.55 GEEK