当前位置: 首页 > 知识库问答 >
问题:

Python,回溯期间返回语句

鲁成天
2023-03-14

我做了一点实验,发生了一些我认为没有预料到的事情。这个问题是基于递归和第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”的测试期间,如果我有返回语句。输出仅为“值1 9 11总计21”,但如果没有return语句,输出为“值1 9 11总计21,值1 9 5 6总计21”。我想知道是否有什么可以解释为什么,我认为return语句会简单地“加速”结束这个方法的递归实例,而不是简单地跳过它无法到达的其他代码行,因为我已经将元组添加到可变集合中,它会被添加到其他递归实例中,因此没有问题。但我当然错了。

共有1个答案

鲍宁
2023-03-14

return语句导致for循环过早终止,因此它可能无法在该特定递归深度找到所有可能的解决方案。

我们可以通过向跟踪循环索引的函数添加一个额外的列表来看到这一点。请注意,我给index一个默认值-这是避免默认可变参数陷阱的标准方法。

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)}

如果我们取消注释返回,我们将得到以下输出:

[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],这意味着[0,0,0]for循环提前终止。

正如我前面提到的,使用可变对象作为默认参数可能是危险的。这在“最小惊讶”和可变默认论点中得到了广泛讨论。

在这段代码中,您不会遇到问题,因为只有一个调用twentyone使用默认参数,递归调用提供显式参数。但是,如果您的呼叫代码在另一个用户输入列表中第二次呼叫了twentyone,那么默认的answer仍将保留上次呼叫中收集的项目。堆栈列表是安全的,因为您从未对其进行变异。

请注意,默认可变参数的行为并不总是一个陷阱。它对于实现缓存非常有用;例如,请参见我对Python中斐波那契的回答。

FWIW,这里有一个版本的twentyone,它没有默认的可变参数问题。我还更改了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

另一种实现方法是作为递归生成器。这样我们就不需要回答,我们只需要找到解决方案就可以了。

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

输出

{(1, 9, 11), (1, 5, 6, 9), (5, 5, 11), (5, 5, 5, 6)}

 类似资料:
  • 问题内容: 我刚刚学习(正在学习)函数参数在Python中的工作方式,并且在没有明显原因的情况下开始进行实验: 给出了输出: 哪里来的?还有,这是什么? 问题答案: 它是函数的返回值,您可以将其打印出来。如果没有语句(或者只是没有参数的),则将隐式添加到函数的末尾。 您可能想返回函数中的值,而不是打印它们:

  • 我正在创建一个递归导航迷宫的程序。代码: 然而,每当我到达死胡同时,它都不会回溯。当我调试时,它表明当程序从递归或“回溯”返回时,我的起始值专注于停留在我的死胡同空间。 例如: 9是我的出发点。2是我的退出。4是我的道路。1 表示墙壁。当我到达一个死胡同时(在本例中为第 7 行,第 2 列)。我的立场是等于整个程序其余部分的死胡同空间。这是为什么呢?

  • pp(probe point)函数可以返回触发当前处理程序的事件名(包含展开了的通配符和别名)。如果该事件与特定的函数相关,pp的输出会包括触发了该事件的函数名。然而,许多情况下触发同一个事件的函数可能来自于程序中不同的模块;特别是在该函数位于某个共享库的情况下。还好SystemTap提供了用户空间栈的回溯(backtrace)功能,便于查看事件是怎么被触发的。 编译器优化代码时会消除栈帧指针(s

  • 问题内容: 您好,我是python的新手,想知道您是否可以帮我一些忙。我一直在玩这段代码,似乎无法使其正常工作。 现在,isPrime方法的输出如下: 我确定该函数应返回true,然后应打印“是”。我想念什么吗? 问题答案: 您将放弃递归调用的返回值: 您也想传播递归调用的值,包括一条语句: 现在,您的代码将输出:

  • 问题内容: 我试图从multiprocessing.Process获取一个追溯对象。不幸的是,通过管道传递异常信息不起作用,因为无法腌制回溯对象: 追溯: 还有另一种访问异常信息的方法吗?我想避免传递格式化的字符串。 问题答案: 使用您可以传递包装的异常并在以后重新引发它们: 因此,如果您在远程进程中捕获到异常,则将其包装,然后将其传递回去。调用主流程即可完成工作。

  • 问题内容: 我有一个包含表的MySQL数据库。 但是,我找不到在Java中将其作为Java中某种对象返回的方法。 我可以打电话给它,它会返回,但这并不好,因为没有办法比较字符串上的日期等。 我也可以打电话,但这根本不返回时间。 问题答案: 您需要使用Thomas的注释中建议的getTime()或getTimestamp()方法。举个例子… 说出要查询的表格,如下所示: 您可以这样做: 如果要使用J