web-dev-qa-db-ja.com

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

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これを実現する慣用的な方法のリストのような構造があります?

104
mistertim

Pythonの配列の合計方法はsumです。他の目的で、reduceoperatorモジュールの組み合わせを使用できる場合があります。

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

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

高階関数の使用は非常にPythonicです。関数やクラスを含むすべてがオブジェクトであるというPythonの原則をうまく利用しています。確かに、ラムダは一部のPythonistaに嫌われていますが、それは主に、複雑になったときに読みにくい傾向があるためです。

107
Fred Foo

ハスケル

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がどの程度かを確認してください。

15
clay

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
12
Kyr

本当に質問に答えるわけではありませんが、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
5
Mehdi Nellen

車輪を再発明することもできます:

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)
4
Zenobe

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

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

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

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

2
JBernardo

Python 3.8を開始し、 代入式(PEP 572):=演算子)の導入により、式の結果に名前を付けることができるため、リストを使用できます。他の言語がfold/foldleft/reduce操作と呼ぶものを複製するための理解:

リスト、還元関数、およびアキュムレーターが与えられた場合:

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

結果のitemsを取得するために、faccumulationを折りたたみます。

[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
1
Xavier Guihot

私はパーティーにかなり遅れるかもしれませんが、単純なラムダ計算とカリー化された関数を使用してカスタム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の値を出力します

1
Pant

この質問の回答者の一部は、fold関数の抽象的なツールとしてのより広い意味を逃していると思います。はい、sumは整数のリストに対して同じことを実行できますが、これは簡単なケースです。 foldはより一般的です。さまざまな形状のデータ構造のシーケンスがあり、集約をきれいに表現したい場合に便利です。したがって、集約変数を使用してforループを作成し、毎回手動で再計算する代わりに、fold関数(またはPythonバージョン、reduceに対応しているように見えます)プログラマは、単に2つのことを提供することで、集約の意図をより明確に表現できます。

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