【问题标题】:How to write foldr (right fold) generator in Python?如何在 Python 中编写 foldr(右折叠)生成器?
【发布时间】:2015-03-31 15:54:19
【问题描述】:

Python 的reduce 是左折叠的,这意味着它是尾递归的,它的用途可以巧妙地重写为循环。但是,Python 没有用于进行正确折叠的内置函数。由于右折叠最自然地是用递归编写的(而且 Python 不像函数式语言那样喜欢递归),所以我有兴趣在生成器方面编写右折叠 (foldr)。

如何做到这一点?更具体地说,如何在 Python 2.7 中完成?

编辑:我应该提到foldr 的好处之一是你有时可以在无限列表中弃牌,而不会冒着吃掉你的筹码的风险。我希望看到保留此属性的答案。

例如,Haskell 的 foldr 在输入和输出上都是惰性的,并且可以允许短路“步进”函数在长/无限输入上工作:

foldr (&&) True (repeat False)  -- gives False

任何使用list/reversed/等的Python变体。如果给定itertools.repeat(some_value),输入将挂起。

请注意,由于严格性,Python 的 reduce 在同一示例中阻塞:

reduce(lambda x, y: x and y, itertools.repeat(False), True) # hangs

【问题讨论】:

  • 我认为为了做到这一点,你的生成器必须yield一个生成器yields一个生成器......如果传递的参数是一个空列表,yield s 初始值......坦率地说,这听起来有点让人头疼...... Python 可以很好地使用递归,只要它具有合理的深度,但我不完全确定递归和生成器可以很好地协同工作。 ..
  • haskell foldr的定义只是reduce,foldl在无限列表上会失败。
  • Python 的 reduce 是左折叠的,所以它就像 Haskell 的 foldl' (注意素数)。是的,foldl 及其变体将在无限列表中失败,这就是为什么人们可能希望使用正确的折叠。
  • 明白了,不幸的是,函数调用在 python 中并不懒惰,所以如果你将foldr 实现为f(x, f(x, f(x, ....))),那么所有函数都将立即得到评估,因此即使函数在无限列表中也会失败被定义为def lor(a,b): a or b,而or 是惰性的,b 参数不是。
  • @achampion 谢谢。在处理这个问题/答案的过程中,我意识到这一点。在选择答案之前,我会花更多时间思考。

标签: python recursion fold


【解决方案1】:

所以一个简单的python生成器(没有适当的错误检查):

def foldr(op, lst):
    l, x = reversed(list(lst)), None
    for i in l:
        if not x:
            x = i
            continue
        x = op(x, i)
        yield x

例如:

>>> from operator import mul
>>> for i in foldr(mul, [1,2,3,4]):
...     print i
24
24
12

几乎与文档中“大致等效”的 reduce 实现相同:

def foldr(function, iterable, initializer=None):
    it = reversed(list(iterable))
    if initializer is None:
        try:
            initializer = next(it)
        except StopIteration:
            raise TypeError('foldr() of empty sequence with no initial value')
    accum_value = initializer
    for x in it:
        accum_value = function(accum_value, x)
        yield accum_value

[编辑] 因此,纯粹作为一种心灵的锻炼并且几乎没有实际价值,只要您折叠的功能之间有一些合作,就可以推迟......例如:

class Defer(object):
    def __init__(self, func, *args):
        self.func = func
        self.args = args
    def __bool__(self):
        return self.func(*self.args)
    def __int__(self):
        return self.func(*self.args)

def foldr(function, iterable, initializer):
    it = iter(iterable)
    try:
        return function(next(it), Defer(foldr, function, it, initializer))
    except StopIteration:
        return initializer

然后,只要函数转换为正确的类型,您就可以推迟计算,但这不适用于原生运算符,所以不确定这到底有多大用处:

>>> print(foldr(lambda a, b: int(a)*int(b), [1,2,3,4], 1))
24

定义一个永久生成器:

from itertools import repeat
def forever():
    yield False
    yield True
    for i in repeat(False):
        yield i

在无限列表中折叠or,找到 True 时返回

>>> print(foldr(lambda a, b: bool(a) or bool(b), forever(), False))
True

【讨论】:

  • 如果lst 是一个生成器呢?
  • 那你需要对iterables进行操作...l = list(lst)。您需要扩展完整的生成器才能执行 foldr。
【解决方案2】:

您必须捕获适当的异常,但应该知道如何迭代:

def foldr(a, b, l):
    if isinstance(l, Iterator):
        it = reversed(list(l))
    else:
        it = reversed(l)
    try:
       nxt = next(it)
    except StopIteration:
        return 
    c = a(nxt, b)  
    stop = object() 
    while nxt is not stop:
        yield c
        nxt = next(it, stop)
        c = a(nxt, c) if nxt is not stop else c



from operator import truediv
for c in (foldr(truediv, 1, [1, 2, 3, 4, 5, 6, 7, 8])):
    print(c)

【讨论】:

  • 如果l 本身就是一个生成器呢?
  • @3noch,您无法反转生成器/迭代器,因此这取决于您认为可接受的替代方案
  • 你能举一个例子吗?它是如何与无限列表一起工作的?
  • 如何在无限列表上做 foldr?
  • @3noch,那么在无限输入时会发生什么,处理n 元素后折叠器会间歇性返回还是短路?
【解决方案3】:

如果你要使用生成器定义一个函数,为什么不使用下面的呢?

def foldr(op, lst):
    return reduce(op, reversed(lst))

【讨论】:

    【解决方案4】:

    我认为你想要这样的东西:

    def foldr(fn, seq, init):
        it = iter(seq)
        try:
            x = next(it)
        except StopIteration:
            try:
                for elem in init:
                    yield elem
            except TypeError:
                yield init
        else:
            try:
                for elem in fn(x, foldr(fn, it, init)):
                    yield elem
            except TypeError:
                yield fn(x, foldr(fn, it, init))
    

    它还没有完全准备好生产,因为它会很快达到 Python 堆栈限制,并且由于对 fn 的双重调用而存在副作用函数时会令人惊讶,但它应该足以给你一个想法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-11
      • 2021-11-12
      • 2017-01-04
      • 2014-09-03
      • 2017-08-16
      • 1970-01-01
      相关资源
      最近更新 更多