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の実装ですが、「Pythonの」プログラミング方法はlambda
を避けることであると言われました用語および高階関数、可能な場合はリスト内包表記を優先します。したがって、リストを折りたたむ好ましい方法、またはPythonのreduce
関数ではない、またはreduce
これを実現する慣用的な方法のリストのような構造があります?
Pythonの配列の合計方法はsum
です。他の目的で、reduce
とoperator
モジュールの組み合わせを使用できる場合があります。
def product(xs):
return reduce(operator.mul, xs, 1)
Haskellの用語では、reduce
は実際にはfoldl
であることに注意してください。折り畳みを実行するための特別な構文はなく、組み込みのfoldr
はありません。また、実際にreduce
を非連想演算子と一緒に使用するのは悪いスタイルと見なされます。
高階関数の使用は非常にPythonicです。関数やクラスを含むすべてがオブジェクトであるというPythonの原則をうまく利用しています。確かに、ラムダは一部のPythonistaに嫌われていますが、それは主に、複雑になったときに読みにくい傾向があるためです。
ハスケル
foldl (+) 0 [1,2,3,4,5]
Python
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]
を好むでしょう。
明らかな便利な機能がない自明でないシナリオの場合、慣用的なPythonのアプローチは、forループを明示的に記述し、reduce
またはfold
を使用する代わりに可変変数の割り当てを使用することです。
それはまったく機能的なスタイルではありませんが、それは「Pythonの」方法です。 Pythonは、機能的な純粋主義者向けには設計されていません。 Pythonがフロー制御の例外をどのように優先するかを参照して、非機能的なイディオムpythonがどの程度かを確認してください。
Python 3では、reduce
が削除されました: リリースノート 。それでも、 functoolsモジュール を使用できます
import operator, functools
def product(xs):
return functools.reduce(operator.mul, xs, 1)
一方、ドキュメントでは、for
の代わりにreduce
- loopを優先しているため、次のようになっています。
def product(xs):
result = 1
for i in xs:
result *= i
return result
本当に質問に答えるわけではありませんが、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
車輪を再発明することもできます:
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)
この(削減)問題に対する実際の答えは、ループを使用するだけです!
initial_value = 0
for x in the_list:
initial_value += x #or any function.
これは、reduceよりも高速であり、PyPyのようなものはそのようなループを最適化できます。
ところで、合計ケースは sum
関数で解決する必要があります
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
私はパーティーにかなり遅れるかもしれませんが、単純なラムダ計算とカリー化された関数を使用してカスタムfoldr
を作成できます。これは、pythonでのfoldrの実装です。
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))
実装は再帰的ですが(遅くなる場合があります)、それぞれ15
および120
の値を出力します
この質問の回答者の一部は、fold
関数の抽象的なツールとしてのより広い意味を逃していると思います。はい、sum
は整数のリストに対して同じことを実行できますが、これは簡単なケースです。 fold
はより一般的です。さまざまな形状のデータ構造のシーケンスがあり、集約をきれいに表現したい場合に便利です。したがって、集約変数を使用してfor
ループを作成し、毎回手動で再計算する代わりに、fold
関数(またはPythonバージョン、reduce
に対応しているように見えます)プログラマは、単に2つのことを提供することで、集約の意図をより明確に表現できます。