関数型プログラミングの「fold」関数に相当する「pythonic」とは何ですか?


115

Haskellで次のようなことを実現する最も慣用的な方法は何ですか?

foldl (+) 0 [1,2,3,4,5]
--> 15

またはRubyでの同等のもの:

[1,2,3,4,5].inject(0) {|m,x| m + x}
#> 15

明らかに、Pythonはreduce上記とまったく同じように、foldの実装である関数を提供しますが、プログラミングの「pythonic」の方法は、lambda項と高次関数を避け、可能な場合はリスト内包を優先することだと言われました。したがって、リスト、またはreduce関数ではないPythonのリストのような構造を折りたたむ好ましい方法はありますか、またはreduceこれを達成する慣用的な方法はありますか?


2
sum十分ではありませんか?
JBernardo 2012

3
これがあなたの質問の良い例かどうかわかりません。これはで簡単に実現できますsum。いくつかの異なるタイプの例を提供したい場合があります。
jamylak 2012

14
Hey JBernardo-数値のリストを合計することは、かなり退屈な例として意図されていました。特に、整数を合計するのではなく、バイナリ演算と開始値を使用してリストの要素を累積するという一般的な考え方に興味があります。
mistertim 2012

1
@mistertim:sum()これを使用すると、実際には限られた機能が提供されます。たとえばsum([[a], [b, c, d], [e, f]], [])戻ります[a, b, c, d, e, f]
Joel Cornett 2012

リストでそれを行う場合は、この手法で注意するべき事柄の良いデモンストレーションです+が、リストでは、時間とメモリの両方で線形時間操作が行われ、呼び出し全体が二次式になります。使用list(itertools.chain.from_iterable([a], [b,c,d],[e,f],[]])は全体的に線形です-そして、それを一度だけ繰り返す必要がある場合は、呼び出しをドロップしてlist、メモリの観点からそれを一定にすることができます。
lvc 2013年

回答:


114

配列を合計するPythonの方法はを使用していsumます。他の目的のために、reducefunctoolsモジュールからの)とモジュールの組み合わせを使用できる場合がありoperatorます。例:

def product(xs):
    return reduce(operator.mul, xs, 1)

Haskellの用語でreduceは、実際にはfoldlであることに注意してください。折りたたみを実行するための特別な構文はなく、組み込みもありませんfoldr。また、実際にreduceは非結合演算子を使用することは悪いスタイルと見なされます。

高次関数の使用はかなりpythonicです。関数やクラスを含むすべてがオブジェクトであるというPythonの原則をうまく利用しています。ラムダは一部のPythonistaに不満を抱いているようですが、その主な理由は、複雑になると読みにくい傾向があるためです。


4
@JBernardo:ビルトインモジュールにないものはpythonicではないと言っていますか?
Fred Foo

4
いいえ、それは馬鹿げたことでしょう。しかし、GvRが組み込み関数からそれを削除する時点で、reduce関数それほど嫌うと思う理由を1つ挙げてください。
JBernardo 2012

6
@JBernardo:人々はそれであまりにもスマートなトリックをしようとするので。そのブログの投稿から引用すると、「の適用範囲はreduce()結合演算子にかなり制限されており、それ以外の場合はすべて、累算ループを明示的に記述するほうがよい」そのため、その使用は制限されていますが、GvRでさえ、標準ライブラリに保持するのに十分なほど有用であることを認めざるを得なかったようです。
Fred Foo

13
@JBernardo、それはHaskellとSchemeでのfoldのすべての使用法が等しく悪いことを意味しますか?それはプログラミングの別のスタイルであり、それを無視して指を耳に入れ、不明確だと言ってもそうはなりません。別のスタイルあるほとんどのものと同様に、それに慣れるには練習が必要です。考えは、物事を一般的なカテゴリーに分類して、プログラムについて推論しやすくすることです。「ああ、私はこれをやりたい、うーん、折りたたみのように見えます」(またはマップ、または展開、または展開してからその上で折りたたみ)
Wes

3
Pythonのラムダに複数の式を含めることはできません。一生懸命頑張っても複雑にすることはできません。したがって、それらを好まないPythonistaはおそらく慣れていないため、関数型プログラミングスタイルを好まないでしょう。
ゴーレム2015年

16

ハスケル

foldl (+) 0 [1,2,3,4,5]

パイソン

reduce(lambda a,b: a+b, [1,2,3,4,5], 0)

明らかに、これはポイントを説明するための簡単な例です。Pythonでは、あなたはただやるだけでありsum([1,2,3,4,5])、Haskellの純粋主義者でさえ一般的に好むでしょうsum [1,2,3,4,5]

明らかな便利な関数がない場合の自明ではないシナリオの場合、慣用的なpythonicアプローチは、forループを明示的に書き出し、reduceまたはを使用する代わりに可変変数の割り当てを使用することfoldです。

それはまったく機能的なスタイルではありませんが、それは「パイソン的な」方法です。Pythonは機能的な純粋主義者向けには設計されていません。Pythonがフロー制御の例外を優先する方法を見て、非機能的な慣用的なPythonがどのようになっているかを確認してください。


11
折り目は、機能的な「純粋主義者」以上に役立ちます。それらは汎用の抽象化です。再帰的な問題はコンピューティングに蔓延しています。フォールドは、ボイラープレートを削除する方法と、再帰をサポートしていない言語で再帰的ソリューションを安全にする方法を提供します。とても実用的なことです。この分野におけるGvRの偏見は残念です。
itsbruce 2017

12

Python 3では、 reduceが削除されました:リリースノート。それでもfunctoolsモジュールを使用できます

import operator, functools
def product(xs):
    return functools.reduce(operator.mul, xs, 1)

一方、ドキュメンテーションはのfor代わりに-loopを優先することを表現しているreduceため、次のようになります。

def product(xs):
    result = 1
    for i in xs:
        result *= i
    return result

7
reducePython 3標準ライブラリから削除されませんでした。あなたが示すreduceようにfunctoolsモジュールに移動しました。
クレイ

@clay、私はちょうどGuidoのリリースノートからそのフレーズを取り入れましたが、あなたは正しいかもしれません:)
Kyr

5

ホイールを再発明することもできます。

def fold(f, l, a):
    """
    f: the function to apply
    l: the list to fold
    a: the accumulator, who is also the 'zero' on the first call
    """ 
    return a if(len(l) == 0) else fold(f, l[1:], f(a, l[0]))

print "Sum:", fold(lambda x, y : x+y, [1,2,3,4,5], 0)

print "Any:", fold(lambda x, y : x or y, [False, True, False], False)

print "All:", fold(lambda x, y : x and y, [False, True, False], True)

# Prove that result can be of a different type of the list's elements
print "Count(x==True):", 
print fold(lambda x, y : x+1 if(y) else x, [False, True, True], 0)

f再帰的なケースでは、引数を入れ替えます。
KayEss 2013年

7
Pythonは末尾再帰を欠いているため、これはより長いリストで壊れ、無駄になります。さらに、これは真の機能を「折る」、しかし、である単に左倍、すなわちfoldlのではない正確にreduceの関数のシグネチャを減らす申し出(ノートがあり、すでにreduce(function, sequence[, initial]) -> valueそれは、あまりにも、初期値を与える機能が含まれています-アキュムレータ)。
cemper93 2014年

5

質問には実際には答えませんが、foldlとfoldrのワンライナー:

a = [8,3,4]

## Foldl
reduce(lambda x,y: x**y, a)
#68719476736

## Foldr
reduce(lambda x,y: y**x, a[::-1])
#14134776518227074636666380005943348126619871175004951664972849610340958208L

2
これはあなたのフォルダを書くためのより良い方法だと思います:reduce(lambda y, x: x**y, reversed(a))。より自然な使用法になり、イテレータで動作し、メモリ消費が少なくなりました。
Mateen Ulhaq 2018年

5

の開始Python 3.8、および割り当て式の導入(PEP 572):=の結果に名前を付ける可能性を与える演算子)、リスト内包表記を使用して、他の言語がfold / foldleft / reduce操作と呼ぶものを複製できます。

リスト、リデュース関数、アキュムレータが与えられた場合:

items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc * x
accumulator = 1

私たちは、折り畳むことができるitemsとのf結果として得を得るために、accumulation

[accumulator := f(accumulator, x) for x in items]
# accumulator = 120

または凝縮された形で:

acc = 1; [acc := acc * x for x in [1, 2, 3, 4, 5]]
# acc = 120

リスト内包の結果は各ステップでの累積の状態を表すため、これは実際には「スキャンレフト」操作でもあることに注意してください。

acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120

3

この(還元)問題に対する実際の答えは、ループを使用するだけです!

initial_value = 0
for x in the_list:
    initial_value += x #or any function.

これはreduceよりも速く、PyPyのようなものはそのようなループを最適化できます。

ところで、合計ケースはsum関数で解決する必要があります


4
これは、このような例ではpythonicとは見なされません。
jamylak 2012

7
Pythonループは非常に遅いです。reducePythonプログラムを最適化する一般的な方法は、使用(または乱用)です。
Fred Foo

1
@larsmansお願い、reduceが単純なループより速いとは言わないでください...反復ごとに常に関数呼び出しのオーバーヘッドがあります。また、PypyはループをCの速度に最適化できます
JBernardo 2012

1
@JBernardo:はい、それは私が主張していることです。私productはあなたのスタイルの1つに対して私のバージョンのプロファイルを作成しましたが、それはより高速です(ただし、わずかに)。
Fred Foo、

1
@JBernardo組み込み関数(などoperator.add)を引数として削減を想定:この追加の呼び出しはC呼び出し(Python呼び出しよりもはるかに安価)であり、数十のバイトコード命令を簡単にディスパッチして解釈することを節約できます。関数呼び出し。

1

この質問の回答者の何人かは、fold抽象的なツールとしての機能の広範な意味合いを見逃していると思います。はい、sum整数のリストに対して同じことを実行できますが、これは簡単なケースです。foldより一般的です。さまざまな形のデータ構造のシーケンスがあり、集計を明確に表現したい場合に役立ちます。そのためfor、集計変数を使用してループを構築し、毎回手動で再計算する代わりに、fold関数(またはreduce対応するように見えるPythonバージョン)を使用すると、プログラマーは単純に次のように指定することで、集計の意図をより明確に表現できます。 2つのこと:

  • 集計のデフォルトの開始値または「シード」値。
  • 集計の現在の値(「シード」で始まる)とリスト内の次の要素を取り、次の集計値を返す関数。

こんにちはrq_!私はあなたの答えが改善され、foldそれをPythonできれいに行うのが難しいという自明ではない例を示し、それからPythonでそれを " fold"するなら、かなり追加するでしょう:-)
Scott Skiles

0

パーティーにはかなり遅れるかもしれませんが、foldr簡単なラムダ計算とカレー関数を使用してカスタムを作成できます。これが、Pythonでのフォルダの実装です。

def foldr(func):
    def accumulator(acc):
        def listFunc(l):
            if l:
                x = l[0]
                xs = l[1:]
                return func(x)(foldr(func)(acc)(xs))
            else:
                return acc
        return listFunc
    return accumulator  


def curried_add(x):
    def inner(y):
        return x + y
    return inner

def curried_mult(x):
    def inner(y):
        return x * y
    return inner

print foldr(curried_add)(0)(range(1, 6))
print foldr(curried_mult)(1)(range(1, 6))

実装は再帰的ですが(遅くなる可能性があります)、値15120それぞれを出力します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.