Python 接下来的问题是递归函数,它解决了在游戏中获得一定点数的所有可能方法

Python 接下来的问题是递归函数,它解决了在游戏中获得一定点数的所有可能方法,python,recursion,Python,Recursion,原始问题(已经解决):假设一个简化的足球计分系统,你可以得到{2,3,7}分,那么在给定分数的情况下,所有可能的得分方式是什么 链接到该问题 我正试图了解这些递归函数在下面的代码中维护score和results变量的方式 代码解决方案(由ggorlen编写): 这很好地解决了我最初的问题。但是,在阅读了编程面试元素(Python)中的一些问题之后,我决定尝试一下传递结果和分数列表的方式。我模仿了作者的一种技术,每次递归调用函数时不传递结果列表,而是为空列表指定一个默认值: def find_sc

原始问题(已经解决):假设一个简化的足球计分系统,你可以得到{2,3,7}分,那么在给定分数的情况下,所有可能的得分方式是什么

链接到该问题

我正试图了解这些递归函数在下面的代码中维护score和results变量的方式

代码解决方案(由ggorlen编写):

这很好地解决了我最初的问题。但是,在阅读了编程面试元素(Python)中的一些问题之后,我决定尝试一下传递结果和分数列表的方式。我模仿了作者的一种技术,每次递归调用函数时不传递结果列表,而是为空列表指定一个默认值:

def find_scoring2(points, ways_to_score=[2, 3, 7]):
    def score_finder(points, scores, result = []):
        if points == 0:
            result.append(scores[:])
        elif points > 0:
            for val in ways_to_score:
                scores.append(val)
                score_finder(points - val, scores)
                scores.pop()
        return result
    return score_finder(points, [])
这产生了相同的结果,这个想法来自EPI第235页(生成平衡括号)

然后,我决定改变分数变量的创建方式,以消除从列表中弹出的需要。我不理解代码,但我还是从同一本书中复制了这个想法

def find_scoring3(points, ways_to_score=[2, 3, 7]):
    def score_finder(points, scores, result = []):
        if points == 0:
            result.append(scores[:])
        elif points > 0:
            for val in ways_to_score:
                #scores.append(val)
                score_finder(points - val, scores + [val])
                #scores.pop()

        return result

    return score_finder(points, [])
因此,我的问题是: 1.我如何在每次都将results变量设置为空列表的情况下,仍然产生正确的解决方案?是否有一些视频或参考资料可以帮助我了解这是如何工作的?
2.为什么当我从追加更改为仅向列表中添加值时,分数变量的行为会发生变化?

第二和第三个版本会意外地工作基于:

发生了什么事?嗯,当定义了
foo
时,就创建了
bar=[]
。未来的调用不会像预期的那样将
bar
设置为新的空列表,而是将
bar
分配给原始列表的别名,该别名会不断累积
42
s

这与直觉相悖,因为我们通常认为这样的函数是无状态的,工作方式如下:

>>> def foo(bar=""):
...     bar += "42"
...     return bar
...
>>> foo()
'42'
>>> foo()
'42'
>>> foo()
'42'
现在让我们移动
foo
baz
的内部函数:

>>> def baz():
...     def foo(bar=[]):
...         bar.append(42)
...         return bar
...     return foo()
...
>>> baz()
[42]
>>> baz()
[42]
>>> baz()
[42]
事实证明,在这种情况下,
foo
是在每次调用
baz
时从零开始创建的,同时还有它的
bar=[]
列表。当
baz
返回时,
foo
被销毁,因为它只是
baz
的堆栈帧中的局部变量,以及
bar

为了让这一点更加明显,这里有一个实验:

>>> def foo(bar=print("bar defined")): pass
...
bar defined
>>> foo()
>>> foo()
>>> foo()
指纹应该很清楚发生了什么。但如果不是,这里有一个递归版本,与您的函数类似,它通过在默认参数上“滥用”别名跨堆栈帧构建结果列表:

>>> def foo(i=4, bar=[]):
...     if i:
...         bar.append(i)
...         return foo(i - 1)
...     return bar
...
>>> foo()
[4, 3, 2, 1]
但是它失败了,比如说,JS:

> const foo = (i=4, bar=[]) => {
... if (i) {
..... bar.push(i);
..... return foo(i - 1);
..... }
... return bar;
... };
undefined
> foo();
[]
和Ruby:

irb(main):023:0> def foo(i=4, bar=[])
irb(main):024:1>   if i > 0
irb(main):025:2>     bar.push(i)
irb(main):026:2>     return foo(i - 1)
irb(main):027:2>   end
irb(main):028:1>   return bar
irb(main):029:1> end
=> :foo
irb(main):030:0> foo
=> []
其中,每次调用
foo()
时,如果缺少
bar
参数,则会将一个新数组(列表)分配给
bar
。我可以展示其他语言示例,但我相信Python在将对象作为默认参数处理方面非常独特

考虑到这一切,“正确”的递归调用应该是
score\u finder(points-val,scores,result)
(类似于
return foo(i-1,bar)
——如果需要,可以在JS/Ruby版本上进行尝试),它将在函数调用一次后填充
result
的默认参数

这就解释了我编写原始代码的动机——我只是完全避免了默认的
def-foo(bar=[])
模式,从而跳过了所有的奇怪之处(不过对于
def-foo(bar=42)
这样的原语也没问题)


至于你的第二个问题,
scores+[val]
看起来更干净,因为没有
append
/
pop
/
[:]
,但它实际上更慢,内存更密集。与其创建一个通过引用传递给调用堆栈的列表(并且仅当结果准备好时才从开始复制到结束),
find\u scoring3
为每个递归调用帧创建一个全新的列表(大量额外工作)。这也意味着
find_scoring3
正在进行不必要的复制(
scores[:]
——尝试删除该片段)


Python的一个缺点是它提供了许多易于使用的O(n)列表操作,如
[:]
中的
列表+[]
,如果使用不当,可能会增加时间复杂度。

谢谢,先生。非常有启发性的东西。你有教授编程的博客或youtube频道吗?没有,就在这里(虽然学的比教的多)。很高兴看到你深入阅读这本书并提出有趣的问题!另外,我忘了回答你的第二个问题--请参阅更新。
>>> def foo(i=4, bar=[]):
...     if i:
...         bar.append(i)
...         return foo(i - 1)
...     return bar
...
>>> foo()
[4, 3, 2, 1]
> const foo = (i=4, bar=[]) => {
... if (i) {
..... bar.push(i);
..... return foo(i - 1);
..... }
... return bar;
... };
undefined
> foo();
[]
irb(main):023:0> def foo(i=4, bar=[])
irb(main):024:1>   if i > 0
irb(main):025:2>     bar.push(i)
irb(main):026:2>     return foo(i - 1)
irb(main):027:2>   end
irb(main):028:1>   return bar
irb(main):029:1> end
=> :foo
irb(main):030:0> foo
=> []