Python,回溯过程中的return语句

时间:2017-01-26 10:23:42

标签: python recursion

我正在尝试一点,发生了一些我认为没想到的事情。 问题是基于递归和在线注释的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语句只是“加速”结束这个方法的递归实例,而不是简单地跳过它不会达到的其他代码行,因为我已经添加了元组对于可变集,它将被添加到其他递归实例中,因此没有问题。但我当然错了。

1 个答案:

答案 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)))