154

在 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使用术语和高阶函数,在可能的情况下更喜欢列表理解。因此,是否存在折叠列表的首选方法,或者 Python 中的类似列表的结构不是reduce函数,或者是reduce实现此目的的惯用方法?

4

8 回答 8

147

对数组求和的 Pythonic 方法是使用sum. 出于其他目的,您有时可以使用reduce(来自functools模块)和operator模块的某种组合,例如:

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

请注意,这reduce实际上是一个foldl,用 Haskell 术语来说。没有特殊的语法来执行折叠,没有内置的foldr,并且实际上reduce与非关联运算符一起使用被认为是不好的风格。

使用高阶函数是非常 Pythonic 的;它很好地利用了 Python 的一切都是对象的原则,包括函数和类。你说得对,一些 Python 爱好者不赞成 lambda,但主要是因为当它们变得复杂时,它们往往不太可读。

于 2012-04-28T18:35:51.123 回答
30

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

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

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

我们可以折叠itemsf获得结果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

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

acc = 1
scanned = [acc := acc * x for x in [1, 2, 3, 4, 5]]
# scanned = [1, 2, 6, 24, 120]
# acc = 120
于 2019-04-28T13:00:57.547 回答
22

哈斯克尔

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].

对于没有明显便利功能的非平凡场景,惯用的 Pythonic 方法是显式写出 for 循环并使用可变变量赋值而不是使用reduceor a fold

这根本不是功能风格,而是“pythonic”方式。Python 不是为功能纯粹主义者设计的。了解 Python 如何支持流控制的异常以了解非功能性惯用 Python 的表现如何。

于 2015-08-19T17:36:27.993 回答
13

在 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
于 2017-04-27T15:20:37.227 回答
7

不是真正回答这个问题,而是 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
于 2017-04-16T14:55:27.797 回答
6

你也可以重新发明轮子:

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)
于 2013-05-21T09:39:01.277 回答
1

这个(减少)问题的实际答案是:只需使用循环!

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

这将比 reduce 更快,并且 PyPy 之类的东西可以优化这样的循环。

顺便说一句,求和的情况应该用sum函数来解决

于 2012-04-28T18:45:41.743 回答
1

我相信这个问题的一些受访者已经错过了fold函数作为抽象工具的更广泛含义。是的,sum可以对整数列表做同样的事情,但这是一个微不足道的案例。fold更通用。当您拥有一系列不同形状的数据结构并希望清晰地表达聚合时,它很有用。因此,不必for每次都必须使用聚合变量构建循环并手动重新计算它,而是一个fold函数(或reduce似乎对应的 Python 版本)允许程序员更清楚地表达聚合的意图,只需提供两件事情:

  • 聚合的默认起始值​​或“种子”值。
  • 一个函数,它获取聚合的当前值(以“种子”开头)和列表中的下一个元素,并返回下一个聚合值。
于 2019-04-04T16:35:07.727 回答