如何在Python中编写foldr(右折)生成器?

时间:2015-03-31 15:54:19

标签: python recursion fold

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

如何做到这一点?而且非常具体,如何在Python 2.7中完成?

编辑:我应该提到foldr的好处之一就是你有时可以折叠无限列表,而不会有堆积活着的风险。我想看看保留这个属性的答案。

例如,Haskell的foldr在输入和输出上都是惰性的,并且可以允许短路"步骤"用于处理长/无限输入的函数:

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

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

请注意,Python的reduce因为严格而在同一个例子中窒息:

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

4 个答案:

答案 0 :(得分:2)

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

e.g:

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

答案 1 :(得分:1)

您必须捕获适当的异常,但应该知道如何迭代地执行此操作:

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)

答案 2 :(得分:0)

如果要使用生成器定义函数,为什么不使用以下内容?

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

答案 3 :(得分:0)

我认为您需要这样的东西

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而在具有副作用功能的情况下令人惊讶,但是它足以给你一个主意。