【问题标题】:What is the 'pythonic' equivalent to the 'fold' function from functional programming?与函数式编程中的“折叠”函数等效的“pythonic”是什么?
【发布时间】:2012-05-09 03:08:35
【问题描述】:

在 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 是实现此目的的惯用方式?

【问题讨论】:

  • sum 还不够好?
  • 不确定这是否是您问题的一个很好的例子。使用sum 可以轻松实现,您可能需要提供一些不同类型的示例。
  • Hey JBernardo - 对数字列表求和是一个相当简陋的例子,我对使用一些二元运算和起始值累积列表元素的一般想法更感兴趣,而不是专门对整数求和。
  • @mistertim: sum() 实际上提供了有限的功能。例如sum([[a], [b, c, d], [e, f]], []) 返回[a, b, c, d, e, f]。
  • 虽然使用列表执行此操作的情况很好地证明了使用此技术需要注意的事项 - + 在列表上是时间和内存中的线性时间操作,使整个调用二次.使用list(itertools.chain.from_iterable([a], [b,c,d],[e,f],[]]) 总体上是线性的 - 如果您只需要迭代一次,您可以放弃对list 的调用以使其在内存方面保持不变。

标签: python list functional-programming reduce fold


【解决方案1】:

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

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

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

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

【讨论】:

  • @JBernardo:你是说内置模块中没有的任何东西都不是pythonic?
  • 不,这样说太愚蠢了。但是,请给我一个单一的理由,为什么您认为 GvR would hate so much the reduce function 会从内置函数中删除它?
  • @JBernardo:因为人们试图用它玩太聪明的把戏。引用那篇博客文章,“reduce() 的适用性几乎仅限于关联运算符,在所有其他情况下,最好明确地写出累积循环。”因此,它的使用是有限的,但即使是 GvR 显然也不得不承认它的用处足以将其保留在标准库中。
  • @JBernardo,这是否意味着在 Haskell 和 Scheme 中每次使用 fold 都同样糟糕?这只是一种不同的编程风格,忽略它并把你的手指放在你的耳朵里并说它不清楚并不会让它如此。像大多数不同风格的东西一样需要练习才能习惯。这个想法是将事物归为一般类别,以便更容易推理程序。 “哦,我想做这个,嗯,看起来像折叠”(或地图,或展开,或展开然后折叠)
  • Python 中的 Lambda 不能包含多个表达式。即使你努力,也无法让它变得复杂。所以不喜欢它们的 Pythonistas 可能只是不习惯,因此不喜欢函数式编程风格。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 这应该是一个可接受的答案,而不是使用sum的建议
  • (Attn: @mistertim) 好的,这就是实际的答案。我很伤心,这不是最佳答案,而且许多其他页面都说 reduce 的 pythonic 类似物是 reduce()列表理解。请把“接受的答案”改成这个
【解决方案3】:

哈斯克尔

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循环并使用可变变量赋值,而不是使用reduce或fold。

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

【讨论】:

  • 折叠不仅对功能性“纯粹主义者”有用。它们是通用抽象。递归问题在计算中无处不在。折叠提供了一种删除样板的方法,以及一种使递归解决方案在本身不支持递归的语言中安全的方法。所以很实用的东西。 GvR 在这方面的偏见是不幸的。
  • JavaScript 比 Python 在语法上更简洁、更有用的 lambda 和高阶函数对我来说非常奇怪。这真是令人不安; Python 是一种设计精良且极具吸引力的语言。
【解决方案4】:

在 Python 3 中,reduce 已被删除:Release notes。不过你可以使用functools module

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

【讨论】:

  • reduce 没有从 Python 3 标准库中删除。如您所见,reduce 已移至 functools 模块。
  • @clay,我刚刚从 Guido 的发行说明中摘录了这句话,但你可能是对的 :)
【解决方案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

【讨论】:

  • 我认为这是编写文件夹的更好方法:reduce(lambda y, x: x**y, reversed(a))。它现在具有更自然的用法,可与迭代器一起使用,并且消耗更少的内存。
【解决方案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)

【讨论】:

  • 您在递归情况下将参数交换为f。
  • 因为 Python 缺少尾递归,这会在更长的列表上中断并且很浪费。此外,这并不是真正的“折叠”功能,而只是一个左折叠,即 foldl,即 完全 reduce 已经提供的功能(请注意,reduce 的函数签名是 reduce(function, sequence[, initial]) -> value - 它,也包括为累加器提供初始值的功能)。
【解决方案7】:

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

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

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

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

【讨论】:

  • 对于像这个这样的例子,这不会被认为是pythonic。
  • Python 循环非常慢。使用(或滥用)reduce 是优化 Python 程序的常用方法。
  • @larsmans 请不要说reduce 比简单循环快...每次迭代都会有函数调用开销。同样,Pypy 可以将循环优化到 C 速度
  • @JBernardo:是的,这就是我所声称的。我刚刚将我的product 版本与您的风格进行了对比,并且它更快(不过,略微)。
  • @JBernardo 假设一个内置函数(如operator.add)作为reduce 的参数:这个额外的调用是一个C 调用(比Python 调用便宜得多),它节省了调度和解释一个几个字节码指令,很容易导致几十个函数调用。
【解决方案8】:

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

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

【讨论】:

  • 嗨,rq_!我认为如果您给出一个在 Python 中很难干净地完成的 fold 的重要示例,然后在 Python 中给出“fold”,那么您的答案将会得到改进并增加很多 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-03
  • 1970-01-01
  • 2015-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多