混淆递归列表附加在Python中

时间:2013-11-22 22:50:30

标签: python list function recursion

我正在尝试创建一对函数,给定一个“起始”数字列表,将递归地添加到每个索引位置,直到达到定义的最大值(就像里程表在汽车中工作的方式一样) - 在重置为1并转移到下一个轮子之前,每个计数轮增加到9。

代码如下所示:

number_list = []

def counter(start, i, max_count):
    if start[len(start)-1-i] < max_count:
        start[len(start)-1-i] += 1
        return(start, i, max_count)
    else:
        for j in range (len(start)):
            if start[len(start)-1-i-j] == max_count:
                start[len(start)-1-i-j] = 1
            else:
                start[len(start)-1-i-j] += 1
                return(start, i, max_count)

def all_values(fresh_start, i, max_count):
    number_list.append(fresh_start)
    new_values = counter(fresh_start,i,max_count)
    if new_values != None:
        all_values(*new_values) 

当我运行 all_values([1,1,1],0,3)并打印 number_list 时,我得到:

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1],    
[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], 
[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], 
[1, 1, 1], [1, 1, 1], [1, 1, 1]]

哪个是不幸的。如果我用

替换 all_values 的第一行,我们就知道了
print(fresh_start)

我得到了我正在追求的东西:

[1, 1, 1]
[1, 1, 2]
[1, 1, 3]
[1, 2, 1]
[1, 2, 2]
[1, 2, 3]
[1, 3, 1]
[1, 3, 2]
[1, 3, 3]
[2, 1, 1]
[2, 1, 2]
[2, 1, 3]
[2, 2, 1]
[2, 2, 2]
[2, 2, 3]
[2, 3, 1]
[2, 3, 2]
[2, 3, 3]
[3, 1, 1]
[3, 1, 2]
[3, 1, 3]
[3, 2, 1]
[3, 2, 2]
[3, 2, 3]
[3, 3, 1]
[3, 3, 2]
[3, 3, 3]

我已经尝试制作 fresh_start 的副本(通过 temp = fresh_start )并附加相应的内容,但输出中没有任何变化。

任何人都可以提供任何有关修复代码的方法吗?关于如何简化问题的反馈也将受到欢迎。

非常感谢!

4 个答案:

答案 0 :(得分:3)

temp = fresh_start

不会复制。附加不会制作副本,作业不会制作副本,而且几乎任何不说副本的副本都不会制作副本。如果您想要一份副本,请将其剪切:

fresh_start[:]

是副本。

答案 1 :(得分:1)

在Python解释器中尝试以下内容:

>>> a = [1,1,1]
>>> b = []
>>> b.append(a)
>>> b.append(a)
>>> b.append(a)
>>> b
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
>>> b[2][2] = 2
>>> b
[[1, 1, 2], [1, 1, 2], [1, 1, 2]]

这是代码中发生的事情的简化版本。但为什么会这样呢?

b.append(a)实际上并未复制a并将其填入b的数组中。它正在为a制作参考。它就像是Web浏览器中的书签:当您使用书签打开网页时,您希望现在看到的网页就像现在一样,而不是当您为其添加书签时。但这也意味着,如果您对同一页面有多个书签,并且该页面发生了变化,无论您关注哪个书签,都会看到更改后的版本。

temp = a的故事相同,就此而言a = [1,1,1]tempa是特定数组的“书签”,恰好包含三个。上面示例中的b是一个数组的书签...其中包含三个包含三个书签的同一个数组。

所以你要做的是创建一个新数组并复制旧数组的元素。最快的方法是采用包含整个数组的数组切片,如user2357112所示:

>>> a = [1,1,1]
>>> b = []
>>> b.append(a[:])
>>> b.append(a[:])
>>> b.append(a[:])
>>> b
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
>>> b[2][2] = 2
>>> b
[[1, 1, 1], [1, 1, 1], [1, 1, 2]]

好多了。

答案 2 :(得分:0)

当我查看所需的输出时,我不禁想到使用其中一个numpy网格数据生成函数。

import numpy
first_column, second_column, third_column = numpy.mgrid[1:4,1:4,1:4]
numpy.dstack((first_column.flatten(),second_column.flatten(),third_column.flatten()))
Out[23]: 
array([[[1, 1, 1],
    [1, 1, 2],
    [1, 1, 3],
    [1, 2, 1],
    [1, 2, 2],
    [1, 2, 3],
    [1, 3, 1],
    [1, 3, 2],
    [1, 3, 3],
    [2, 1, 1],
    [2, 1, 2],
    [2, 1, 3],
    [2, 2, 1],
    [2, 2, 2],
    [2, 2, 3],
    [2, 3, 1],
    [2, 3, 2],
    [2, 3, 3],
    [3, 1, 1],
    [3, 1, 2],
    [3, 1, 3],
    [3, 2, 1],
    [3, 2, 2],
    [3, 2, 3],
    [3, 3, 1],
    [3, 3, 2],
    [3, 3, 3]]])

当然,这种特殊方法的实用性可能取决于您需要处理的各种输入,但我怀疑这可能是构建数据的有趣方式,而numpy对于这种事情来说非常快。大概如果你的输入列表有更多的元素,你可以有更多的min:max参数输入mgrid []然后以类似的方式解包/堆栈。

答案 3 :(得分:0)

这是您的程序的简化版本,可以使用。评论将随之而来。

number_list = []

def _adjust_counter_value(counter, n, max_count):
    """
    We want the counter to go from 1 to max_count, then start over at 1.
    This function adds n to the counter and then returns a tuple:
    (new_counter_value, carry_to_next_counter)
    """
    assert max_count >= 1
    assert 1 <= counter <= max_count

    # Counter is in closed range: [1, max_count]
    # Subtract 1 so expected value is in closed range [0, max_count - 1]
    x = counter - 1 + n
    carry, x = divmod(x, max_count)

    # Add 1 so expected value is in closed range [1, max_count]
    counter = x + 1
    return (counter, carry)

def increment_counter(start, i, max_count):
    last = len(start) - 1 - i
    copy = start[:]  # make a copy of the start

    add = 1  # start by adding 1 to index
    for i_cur in range(last, -1, -1):
        copy[i_cur], add = _adjust_counter_value(copy[i_cur], add, max_count)
        if 0 == add:
            return (copy, i, max_count)
    else:
        # if we have a carry out of the 0th position, we are done with the sequence
        return None

def all_values(fresh_start, i, max_count):
    number_list.append(fresh_start)
    new_values = increment_counter(fresh_start,i,max_count)
    if new_values != None:
        all_values(*new_values)

all_values([1,1,1],0,3)

import itertools as it
correct = [list(tup) for tup in it.product(range(1,4), range(1,4), range(1,4))]
assert number_list == correct

由于您希望计数器从1到max_count包含,所以更新每个计数器有点棘手。您的原始解决方案是使用多个if语句,但在这里我创建了一个使用divmod()来计算每个新数字的辅助函数。这允许我们向任何数字添加任何增量,并找到正确的数字执行。

您的原始程序从未更改i的值,因此我修改过的程序也没有。您可以通过删除i并让increment_counter()始终转到最后位置来进一步简化程序。

如果您在不调用forbreak的情况下运行return循环,则else:情况会在存在的情况下运行。在这里,我添加了一个else:案例来处理列表中第0位的执行。如果存在第0位的执行,则表示我们已到达计数器序列的末尾。在这种情况下,我们返回None

您的原始程序有点棘手。它在return中有两个显式counter()语句,在序列末尾有一个隐式返回。它确实返回None表示递归可以停止,但它的方式对我来说太棘手了。我推荐使用明确的return None

请注意,Python有一个模块itertools,其中包含一种生成这样的计数器系列的方法。我用它来检查结果是否正确。

我确信你正在写这篇文章来了解递归,但是请注意Python不是像这样的递归解决方案的最佳语言。 Python有一个相对较浅的递归堆栈,并且不会自动将尾递归转换为迭代循环,因此如果递归调用嵌套足够多次,这可能会导致Python内部的堆栈溢出。 Python中最好的解决方案是使用itertools.product(),就像我直接生成所需的计数器序列一样。

由于生成的序列是列表列表,而itertools.product()生成元组,我使用列表推导将每个元组转换为列表,因此最终结果是列表列表,我们可以简单地使用用于比较它们的Python ==运算符。