如何从任意深度的递归中获益



我已经编写了一个函数来创建任意长度的输入组合,所以递归似乎是显而易见的方法。虽然一个小玩具示例可以返回结果列表,但我希望生成它们。我读过关于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使我的列表一次生成一个组合的语法是什么?

递归生成器是一个强大的工具,我很高兴您投入精力来研究它们。

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

您将yield from放在表达式from前面,结果应为yielded;在您的情况下,递归调用。因此:yield from place_nth_element(nth+1, pos+1, output)。其思想是,递归调用的生成器的每个结果from都被迭代(在幕后(,并且yield在过程的这一点上被迭代。

请注意,要使其工作:

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

  • 致";收集";结果生成器的结果,您需要迭代顶级调用的结果。幸运的是,迭代在很多地方都是内置的;例如,您只需调用list,它就会为您迭代。

我更喜欢将递归生成器作为一个单独的辅助函数来编写,而不是将其嵌套在包装函数中。由于不再需要从递归访问output_list,因此不需要形成闭包;和CCD_ 12。然而,这确实意味着我们需要通过递归传递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))
]