我正在尝试一点,发生了一些我认为没想到的事情。 问题是基于递归和在线注释的return语句 7
def twentyone(nums, stack = [], answer = set()):
for index, num in enumerate(nums):
new_stack = stack + [num]
total = sum(new_stack)
if total == 21:
answer.add(tuple(new_stack))
#return
elif total < 21:
twentyone(nums[index + 1:], new_stack, answer)
return answer
user_input = input()
list_format = [int(x) for x in user_input.split()]
answer = twentyone(list_format)
if len(answer) == 0:
print("No combination of numbers add to 21")
for solution in answer:
print("The values ", end = "")
for number in solution:
print("{} ".format(number), end = "")
print("add up to 21")
我的问题是,在测试期间使用示例输入“1 9 11 5 6”,如果我有return语句。输出仅为“值1 9 11加起来为21”,但没有return语句,输出为“值1 9 11加起来为21” 值1 9 5 6加起来为21“。 我想知道是否有什么可以解释原因,我认为return语句只是“加速”结束这个方法的递归实例,而不是简单地跳过它不会达到的其他代码行,因为我已经添加了元组对于可变集,它将被添加到其他递归实例中,因此没有问题。但我当然错了。
答案 0 :(得分:2)
return
语句会导致for
循环过早终止,因此可能无法在该特定递归深度找到所有可能的解决方案。
我们可以通过向跟踪for
循环索引的函数添加一个额外的列表来看到这一点。请注意,我给indices
一个默认值None
- 这是避免默认可变参数陷阱的标准方法。
def twentyone(nums, stack = [], answer = set(), indices = None):
if indices is None:
indices = []
for index, num in enumerate(nums):
new_stack = stack + [num]
total = sum(new_stack)
if total == 21:
print(indices + [index], new_stack)
answer.add(tuple(new_stack))
#return
elif total < 21:
twentyone(nums[index + 1:], new_stack, answer, indices + [index])
return answer
user_input = '1 9 11 5 6 3 7'
list_format = [int(x) for x in user_input.split()]
answer = twentyone(list_format)
print('\n', answer)
<强>输出强>
[0, 0, 0] [1, 9, 11]
[0, 0, 1, 0] [1, 9, 5, 6]
[0, 1, 1, 0] [1, 11, 6, 3]
[1, 1, 2] [9, 5, 7]
[2, 2, 0] [11, 3, 7]
[3, 0, 0, 0] [5, 6, 3, 7]
{(9, 5, 7), (1, 11, 6, 3), (1, 9, 5, 6), (1, 9, 11), (5, 6, 3, 7), (11, 3, 7)}
如果我们取消评论return
,我们会收到此输出:
[0, 0, 0] [1, 9, 11]
[0, 1, 1, 0] [1, 11, 6, 3]
[1, 1, 2] [9, 5, 7]
[2, 2, 0] [11, 3, 7]
[3, 0, 0, 0] [5, 6, 3, 7]
{(9, 5, 7), (5, 6, 3, 7), (1, 11, 6, 3), (1, 9, 11), (11, 3, 7)}
索引中缺少 [0, 0, 1, 0]
,这意味着for
的{{1}}循环过早终止。
正如我前面提到的,将可变对象用于默认参数可能很危险。这在“Least Astonishment” and the Mutable Default Argument广泛讨论。
在此代码中,您不会遇到问题,因为您只有一个使用默认args的[0, 0, 0]
调用,而递归调用提供了显式args。但是,如果您的调用代码第二次使用另一个用户输入列表调用twentyone
,则默认twentyone
仍会包含在上一次调用期间收集的项目。 answer
列表是安全的,因为你永远不会改变它。
请注意,默认可变参数的行为并不总是存在缺陷。它对实现缓存非常有用;请参阅我对Fibonacci in Python的回答。
FWIW,这里的stack
版本没有默认的可变参数问题。我也改变了twentyone
&amp; stack
成元组。这可以节省new_stack
电话;使用tuple
创建新元组的效率不亚于使用new_stack = stack + (num,)
创建新列表。元组比列表稍微有效,并且使用new_stack = stack + [num]
/ stack
的列表没什么意义,因为它们永远不会发生变异。
new_stack
实现此功能的另一种方法是作为递归生成器。这样我们就不需要def twentyone(nums, stack=(), answer=None):
if answer is None:
answer = set()
for index, num in enumerate(nums):
new_stack = stack + (num,)
total = sum(new_stack)
if total == 21:
answer.add(new_stack)
elif total < 21:
twentyone(nums[index + 1:], new_stack, answer)
return answer
,我们只是在找到它们时得到解决方案。
answer
<强>输出强>
def twentyone(nums, stack=()):
for index, num in enumerate(nums):
new_stack = stack + (num,)
total = sum(new_stack)
if total == 21:
yield new_stack
elif total < 21:
yield from twentyone(nums[index + 1:], new_stack)
user_input = '1 9 11 5 6 3 7'
list_format = [int(x) for x in user_input.split()]
for t in twentyone(list_format):
print(t)
这样做的缺点是,如果(1, 9, 11)
(1, 9, 5, 6)
(1, 11, 6, 3)
(9, 5, 7)
(11, 3, 7)
(5, 6, 3, 7)
中有重复的项目,那么我们会得到重复的解决方案。但是我们可以通过在nums
:
set()
<强>输出强>
print(set(twentyone(list_format)))
然而,这只能消除完全重复,它并没有摆脱以前解决方案排列的解决方案。为了使它完全万无一失,我们需要对输出元组进行排序。
{(9, 5, 7), (1, 11, 6, 3), (1, 9, 5, 6), (1, 9, 11), (5, 6, 3, 7), (11, 3, 7)}
<强>输出强>
def twentyone(nums, stack=()):
for index, num in enumerate(nums):
new_stack = stack + (num,)
total = sum(new_stack)
if total == 21:
yield tuple(sorted(new_stack))
elif total < 21:
yield from twentyone(nums[index + 1:], new_stack)
user_input = '1 9 5 11 5 6 5'
list_format = [int(x) for x in user_input.split()]
print(set(twentyone(list_format)))