功能风格的Conjoin功能

时间:2011-08-17 12:52:29

标签: python functional-programming

最近,在阅读Python "Functional Programming HOWTO"时,我遇到了一个提到test_generators.py标准模块,在那里我找到了以下生成器:

# conjoin is a simple backtracking generator, named in honor of Icon's
# "conjunction" control structure.  Pass a list of no-argument functions
# that return iterable objects.  Easiest to explain by example:  assume the
# function list [x, y, z] is passed.  Then conjoin acts like:
#
# def g():
#     values = [None] * 3
#     for values[0] in x():
#         for values[1] in y():
#             for values[2] in z():
#                 yield values
#
# So some 3-lists of values *may* be generated, each time we successfully
# get into the innermost loop.  If an iterator fails (is exhausted) before
# then, it "backtracks" to get the next value from the nearest enclosing
# iterator (the one "to the left"), and starts all over again at the next
# slot (pumps a fresh iterator).  Of course this is most useful when the
# iterators have side-effects, so that which values *can* be generated at
# each slot depend on the values iterated at previous slots.

def simple_conjoin(gs):

    values = [None] * len(gs)

    def gen(i):
        if i >= len(gs):
            yield values
        else:
            for values[i] in gs[i]():
                for x in gen(i+1):
                    yield x

    for x in gen(0):
        yield x

我花了一段时间才明白它是如何运作的。它使用可变列表values来存储迭代器的结果,而N + 1迭代器返回values,它通过整个迭代器链。

当我在阅读函数式编程时偶然发现这段代码时,我开始思考是否可以使用函数式编程(使用itertools module中的函数)重写这个连接生成器。 有许多以函数式编写的例程(只需看一下Recipes部分中this文章的末尾)。

但遗憾的是,我还没有找到任何解决方案。

那么,是否可以使用itertools module使用函数式编程来编写这个连接生成器?

由于

2 个答案:

答案 0 :(得分:3)

这似乎有效,但它仍然很懒惰:

def conjoin(gs):
    return [()] if not gs else (
        (val,) + suffix for val in gs[0]() for suffix in conjoin(gs[1:])
    )

def range3():
    return range(3)

print list(conjoin([range3, range3]))

输出:

[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

显示可变状态的示例用法:

x = ""
def mutablerange():
    global x
    x += "x"
    return [x + str(i) for i in range(3)]

print list(conjoin([range3, mutablerange]))

输出:(观察越来越多的'x')

[(0, 'x0'), (0, 'x1'), (0, 'x2'), (1, 'xx0'), (1, 'xx1'), (1, 'xx2'), (2, 'xxx0'), (2, 'xxx1'), (2, 'xxx2')]

如果我们使用itertools.product

x = ""
print list(itertools.product(range3(), mutablerange()))

结果如下:

[(0, 'x0'), (0, 'x1'), (0, 'x2'), (1, 'x0'), (1, 'x1'), (1, 'x2'), (2, 'x0'), (2, 'x1'), (2, 'x2')]

因此,有人清楚地看到,itertools.product缓存了迭代器返回的值。

答案 1 :(得分:2)

simple_conjoin使用相同的基本构建块 - 循环,条件和yield - 作为itertools食谱的构建块。它还将函数视为数据,是函数式编程的标志。

  

当然,这是最有用的   迭代器有副作用,因此可以生成可以生成的值   每个槽都取决于先前槽位迭代的值。

然而,这与函数式编程的工作方式相反。在函数式编程中,每个函数都接受输入并产生输出,并以其他方式对程序的其余部分作出反应。

simple_conjoin中,函数不进行任何输入,并且有副作用。这是它的使用的核心。

因此,虽然您可以在功能样式中编写,但它在简单翻译中无用。

你需要找到一种方法来编写它,这样它才能在没有副作用的情况下运行,然后才能产生真正的“功能”实现。

注意:@递归的答案很好,但是如果range3有副作用,它就不会真正起作用。