什么是'pythonic'相当于函数式编程的'fold'函数?

mis*_*tim 108 python reduce functional-programming list fold

在Haskell中实现类似以下内容的最惯用方法是什么:

foldl (+) 0 [1,2,3,4,5]
--> 15
Run Code Online (Sandbox Code Playgroud)

或者它在Ruby中的等价物:

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

显然,Python提供了reduce函数,这是fold的实现,完全如上所述,然而,有人告诉我,'pythonic'编程方式是避免使用lambda术语和高阶函数,在可能的情况下更喜欢列表推导.因此,有没有一种首选的方法来折叠Python中的列表或类似列表的结构,而不是reduce函数,或者是reduce实现这一目的的惯用方法?

Fre*_*Foo 109

Pythonic总结数组的方法是sum.对于其他目的,您可以有时使用的某种组合reducefunctools模块,如

def product(xs):
    return reduce(operator.mul, xs, 1)
Run Code Online (Sandbox Code Playgroud)

请注意,在Haskell术语中,operator实际上是一个reduce.没有特殊的语法来执行折叠,没有内置foldl,实际上使用foldr非关联运算符被认为是坏样式.

使用高阶函数是相当pythonic; 它充分利用了Python的原则,即一切都是一个对象,包括函数和类.你是对的,一些Python会员不赞同lambdas,但主要是因为当它们变得复杂时它们往往不具有可读性.

  • @JBernardo,这是否意味着Haskell和Scheme中每次使用fold都同样糟糕?这只是一种不同的编程风格,忽略它并将你的手指放在你的耳朵里并说它不清楚是不是这样.像大多数不同风格的东西_it需要练习才能习惯它.我们的想法是将事物放入一般类别,以便更容易推理程序."哦,我想这样做,嗯,看起来像一个折叠"(或地图,或展开,或展开然后折叠) (12认同)
  • @JBernardo:因为人们试图用它来玩太聪明的技巧.引用该博客文章,"reduce()`的适用性几乎仅限于关联运算符,在所有其他情况下,最好明确地写出累积循环." 因此,它的使用是有限的,但即使是GvR显然也不得不承认它足够有用,可以将它保存在标准库中. (6认同)
  • @JBernardo:你说内置模块中的任何东西都不是pythonic? (4认同)
  • 不,这是愚蠢的说法.但是,给我一个理由,为什么你认为[GvR会非常讨厌减少功能](http://www.artima.com/weblogs/viewpost.jsp?thread=98196)从内置中删除它? (4认同)
  • @Wes:我主要考虑传递给`reduce`或`map`的函数来执行副作用,或者接受和返回长元组以一次执行太多的计算.在Haskell中,使用`foldl`有时可能是唯一合理的选项,但即使在Scheme中,也有一个循环变得如此复杂以至于你想要一个名为`let`的点. (2认同)
  • Python中的Lambda不能包含多个表达式.即使你努力,你也无法使它变得复杂.因此,不喜欢它们的Pythonistas可能只是不习惯,因此不喜欢函数式编程风格. (2认同)

Xav*_*hot 17

开始Python 3.8,并引入赋值表达式 (PEP 572):=运算符),它提供了命名表达式结果的可能性,我们可以使用列表理解来复制其他语言称为 fold/foldleft/reduce 操作的内容:

给定一个列表、一个归约函数和一个累加器:

items = [1, 2, 3, 4, 5]
f = lambda acc, x: acc * x
accumulator = 1
Run Code Online (Sandbox Code Playgroud)

我们可以折叠itemsf在为了获得所得accumulation

[accumulator := f(accumulator, x) for x in items]
# accumulator = 120
Run Code Online (Sandbox Code Playgroud)

或以浓缩形式:

acc = 1; [acc := acc * x for x in [1, 2, 3, 4, 5]]
# acc = 120
Run Code Online (Sandbox Code Playgroud)

请注意,这实际上也是一个“scanleft”操作,因为列表推导式的结果代表了每一步的累积状态:

acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120
Run Code Online (Sandbox Code Playgroud)

  • 这应该是一个可接受的答案,而不是使用“sum”的建议 (11认同)
  • 但这并不是真正的折叠。如前所述,这是一个“左扫描”,它会丢弃之后的临时列表。对于一个很长的列表来说,这种差异可能很重要。 (4认同)
  • (收件人:@mistertim)好的,这就是实际的答案。我很伤心这不是最重要的答案,而且许多其他页面都说reduce的pythonic模拟是,好吧,reduce()为了Googlability:reduce的pythonic模拟使用:=将累加器引入到列表理解。请将“接受的答案”更改为此 (2认同)

cla*_*lay 15

哈斯克尔

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或a fold.

这根本不是功能风格,但那是"pythonic"方式.Python不是为功能纯粹主义者设计的.了解Python如何支持流控制的异常,以了解非功能性的idiomatic python是如何实现的.

  • 折叠对于功能性更强的"纯粹主义者"是有用的.它们是通用抽象.递归问题在计算中普遍存在.Folds提供了一种删除样板的方法,以及一种在本身不支持递归的语言中使递归解决方案安全的方法.所以非常实用.GvR在这方面的偏见是不幸的. (8认同)
  • 对我来说,JavaScript 比 Python 拥有语法更清晰、更有用的 lambda 和高阶函数,这让我感到非常奇怪。这实在令人心烦意乱;除此之外,Python 是一种设计精良且有吸引力的语言。 (6认同)

Kyr*_*Kyr 12

在Python 3中,reduce已删除:发行说明.不过你可以使用functools模块

import operator, functools
def product(xs):
    return functools.reduce(operator.mul, xs, 1)
Run Code Online (Sandbox Code Playgroud)

另一方面,文档表达了对for-loop的偏好,而不是reduce:

def product(xs):
    result = 1
    for i in xs:
        result *= i
    return result
Run Code Online (Sandbox Code Playgroud)

  • `reduce`没有从Python 3标准库中删除.当你显示时,`reduce`移动到`functools`模块. (6认同)

Fré*_*ric 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)
Run Code Online (Sandbox Code Playgroud)

  • 因为Python缺少尾递归,所以这会在较长的列表上中断并且很浪费。此外,这并不是真正的“折叠”功能,而仅仅是左折叠,即foldl,即“ reduce”已经提供的“完全”(请注意,reduce的功能签名是“ reduce(function,sequence [,initial]”)。 )-> value`-它也包括为累加器提供初始值的功能)。 (7认同)

Meh*_*len 5

并不是真的回答这个问题,而是折叠和折叠的一线:

a = [8,3,4]

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

## Foldr
reduce(lambda x,y: y**x, a[::-1])
#14134776518227074636666380005943348126619871175004951664972849610340958208L
Run Code Online (Sandbox Code Playgroud)

  • 我认为这是写文件夹的更好方法:`reduce(lambda y,x:x ** y,reversed(a))`。现在,它具有更自然的用法,可与迭代器一起使用,并且消耗更少的内存。 (2认同)