Home > Python > Pythonのリスト内包表記でreduceは実現できる?

Pythonのリスト内包表記でreduceは実現できる?

  • 2007-05-18 (金) 23:08
  • Python

mapやfilterはそのままだからすぐに内包表記でOK。
本当ならreduceもループじゃなくってリスト内包表記で書けたらいいのに。

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> b = 0
>>> def funcA(x):
… global b
… b += x
… return b

>>> [funcA(x) for x in a]
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
>>> b
55
>>>

うーん。
globalよりはClassのほうがまし?

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> class ClassA:
… b = 0
… def funcA(self, x):
… self.b += x
… return self.b

>>> c = ClassA()
>>> [c.funcA(x) for x in a]
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
>>> c.b
55
>>>

うーん。。。。。。
普通にループにするか、複雑な処理だったらリストを処理する関数(中身はループだけど)を書いたほうがましかな。

※ 2007/5/19 5:00ちょっと前 追記
今、夢の中で誰かが破壊的代入がダメなんだよと言ってたので、ちょっと考えてみた。

普通にreduceだったらこう。
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> def funcA(x, y):
… return x + y

>>> reduce(funcA, a, 0)
55
>>>

で、さっきはreduceっぽい処理をグローバル変数への破壊的代入でなんとかしようとしてみた。
じゃー、破壊的代入をしないとこんな感じ?
>>> def funcB(func, xs, init):
… if len(xs)==0:
… return init
… elif len(xs)==1:
… return func(xs[0], init)
… else:
… return func(xs[0], funcB(func, xs[1:], init))

>>> funcB(funcA, a, 0)
55
>>>

これをどうにかして無理やり内包表記でやろうとすると、、、
無さそうですね、少なくとも私にはわかりません。。。

やっぱり夢は夢だったということで。
さっきは酔っ払ってて、今は寝ぼけてて、あんまり筋の良くない話になってるのかな。

無理矢理でもいいので、だれかreduceの代替案を知ってたら教えてください。

※追記 2007/05/21
reduceが廃止されたらやだなーと思って酔っ払って勢いで書いてしまった記事です

Comments:9

ふつうに 07-05-19 (土) 22:51

sum([x for x in a])
じゃだめなの?

もちろん 07-05-19 (土) 22:54

シーケンスの単なる加算なら
sum(a)でいいのだけど。

nmasatomo 07-05-19 (土) 23:36

たしかに例だったらsumで十分ですね。
ただ、もうちょっと複雑なことをやりたいときはどうなのかなーと思って。
一般的に、reduceで複雑なことやりたいというの自体レアケースなのかなぁ。

tane 07-05-31 (木) 17:27

y = 0
func = lambda x,y: x+y
[y for x in range(1,11) for y in [func(x,y)]][-1]
>> 55

こういう感じでどうでしょうか。
generator式は個々の式がclosureになるようですが、二回目のforのループ変数はgenerator式の変数になるので、これを保存用に使ってます。yのinitial valueの設定はもう少し上手くできるかもしれません。

tane 07-05-31 (木) 17:30

失礼、外側のループ変数はgenerator式固有になる、の間違いです。

tane 07-05-31 (木) 22:30

済みません、generator式をlist内包で読みかえて下さい。reduce()だとgenerator式はあまり良くなさそうです。

nmasatomo 07-06-01 (金) 7:48

こんなことができるんですね!
すごいなぁ。
「外側のループ変数を内側のループで使って、内側ループ変数を保存用にする」
というのは言われれば確かにできそうな気がしますが、まったく気づきませんでした。
多用すると他人が読みづらいソースになりそうだけど、このやり方はいろいろ使えそうに思いました。
ありがとうございます!

tane 07-06-01 (金) 18:33

少し調べてみたのですが、generator式の個々の式がclosureのようだというのは誤りでした。ループ変数が引き継がれないのでそう勘違いしたのですが、詳しくはPEP 289の詳細の3をご覧下さい。
http://homepage3.nifty.com/text/script/python/pep-0289.ja.html

list内包もいずれループ変数が保護されるということなので、私の書いたやり方は使えなくなるようです。

list内包でreduce()を行うときの問題は、list内包やgenerator式のループ変数で代入ができない点にあるように思います(例えば、[m=func(m,x) for x in list]と書ければ良いのですが、これは構文エラーです)。言い換えれば、明示的な代入を行わずに計算結果をメモする方法ということになります。globalやclassを使った解法が本質的にはこれを行っていることになります。
別解としては、Pythonの関数オブジェクトは変数を持てますので、以下のように書けます。

def func(x):
__func.m = (lambda x,y:x+y)(func.m,x)
__return func.m
func.m = 0
[func(x) for x in range(1,11)]
>>[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

これも初期値の設定があまり上手くないですね。ちなみにこのやり方だとgenerator式でも問題ないです。list内包やgenerator式の内部にメモ変数を持つことができると綺麗に書けるのかもしれませんが…。
ただ、書いていて思いましたが、reduce()だと集約された結果のみ出てきますが、generator式でreduce()を書くと途中経過を低コストで得られるという利点はありますね。以上、長々と済みません。

nmasatomo 07-06-01 (金) 23:39

ありがとうございます。かなり勉強になりました。

taneさんに教えていただいたやり方が将来的に使えなくなるのは残念ですが、分かりやすい唯一の解を求めるPythonとしてはそのほうが自然なのかもしれませんね。

メモ変数を気軽に使えると便利だとも思いますが、リストを受け取って結果を返す関数を利用したほうが、明示的でPython的なのかもしれないなーとも思いました。

PEP 289の最後に記述されている簡約関数の話が、reduceの代替なのかもしれませんね。

Comment Form
Remember personal info

Trackbacks:0

Trackback URL for this entry
http://blog.joyfullife.jp/archives/2007/05/18230807.php/trackback
Listed below are links to weblogs that reference
Pythonのリスト内包表記でreduceは実現できる? from 30からのBlog

Home > Python > Pythonのリスト内包表記でreduceは実現できる?

Search
Feeds
Meta

Return to page top