python3.5-如何从任意深度的递归产生?

时间:2020-08-28 15:25:58

标签: python recursion yield

我已经编写了一个函数来创建任意长度的输入组合,因此递归似乎是显而易见的方法。虽然可以用一个小的玩具示例返回结果列表,但我想改为产生它们。我已经读过yield from了,但是还没有完全理解它的用法,这些示例似乎还没有涵盖我的用例,并且希望不将其插入我的代码中还没有产生任何东西。作品。请注意,编写此递归代码是我的python能力的极限,因此有大量的调试打印语句。

这是工作清单返回码,其中我希望的非工作收益已被注释掉。

def allposs(elements, output_length):
    """
    return all zero insertion paddings of elements up to output_length maintaining order

    elements -         an iterable of length >= 1
    output_length      >= len(elements)

    for instance allposs((3,1), 4) returns
    [[3,1,0,0], [3,0,1,0], [3,0,0,1], [0,3,1,0], [0,3,0,1], [0,0,3,1]]
    """

    output_list = []

    def place_nth_element(nth, start_at, output_so_far):
        # print('entering place_nth_element with nth =', nth,
        #      ', start_at =', start_at,
        #      ', output_so_far =', output_so_far)
        
        last_pos = output_length - len(elements) + nth
        # print('iterating over range',start_at, 'to', last_pos+1)
        for pos in range(start_at, last_pos+1):
            output = list(output_so_far)           
            # print('placing', elements[nth], 'at position', pos)
            output[pos] = elements[nth]

            if nth == len(elements)-1:
                # print('appending output', output)
                output_list.append(output)
                # yield output    
            else:
                # print('making recursive call')
                place_nth_element(nth+1, pos+1, output)
   
    place_nth_element(0, 0, [0]*output_length)
    return output_list

if __name__=='__main__':
    for q in allposs((3,1), 4):
        print(q)

使用yield from使我的列表一次生成组合的语法是什么?

1 个答案:

答案 0 :(得分:2)

递归生成器是一个功能强大的工具,很高兴您正在努力研究它们。

使用yield from来一次生成列表的语法是什么?

您将yield from放在表达式from的前面,应该对结果进行yield;在您的情况下,递归调用。因此:yield from place_nth_element(nth+1, pos+1, output)。这样做的想法是,在过程的这一点上,将每个结果from(称为递归生成器)遍历(在幕后)并yield进行迭代。

请注意,此方法可以起作用:

  • 您需要在递归的基础级别上yield单个结果

  • 要“收集”结果生成器的结果,您需要遍历顶级调用的结果。幸运的是,迭代内置在很多地方。例如,您可以调用list,它将为您进行迭代。

我不是将递归生成器嵌套在包装函数中,而是将其编写为单独的辅助函数。由于不再需要从递归访问output_list,因此无需形成闭包;和flat is better than nested,如他们所说。但是,这确实意味着我们需要通过递归传递elements。我们不需要传递output_length,因为我们可以重新计算它(output_so_far的长度在递归中是恒定的)。

此外,我发现在执行这类算法时,尽可能地发挥功能(在范式意义上,即避免副作用和可变性,并通过创建新对象来进行思考)是有帮助的。您有一种使用list进行复制的可行方法(尽管使用.copy方法更清晰),但是我认为有一种更简洁的方法,如下所示。

所有这些建议使我们能够:

def place_nth_element(elements, nth, start_at, output_so_far):        
    last_pos = len(output_so_far) - len(elements) + nth
    for pos in range(start_at, last_pos+1):
        output = output_so_far[:pos] + (elements[nth],) + output_so_far[pos+1:]
        if nth == len(elements)-1:
            yield output    
        else:
            yield from place_nth_element(elements, nth+1, pos+1, output)


def allposs(elements, output_length):
    return list(place_nth_element(elements, 0, 0, (0,)*output_length))

但是,我不会以这种方式解决问题-因为标准库已经提供了一个整洁的解决方案:我们可以找到值应该去到的索引itertools.combinations,然后将其插入。现在我们不再需要递归地思考,我们可以继续修改值了:)

from itertools import combinations

def place_values(positions, values, size):
    result = [0] * size
    for position, value in zip(positions, values):
        result[position] = value
    return tuple(result)


def possibilities(values, size):
    return [
        place_values(positions, values, size)
        for positions in combinations(range(size), len(values))
    ]
相关问题