我已经编写了一个函数来创建任意长度的输入组合,所以递归似乎是显而易见的方法。虽然一个小玩具示例可以返回结果列表,但我希望生成它们。我读过关于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
前面,结果应为yield
ed;在您的情况下,递归调用。因此: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))
]