最优雅的方式逐步构建列表?

6 投票
5 回答
8637 浏览
提问于 2025-04-19 00:55

我在用Python做一些事情,想知道最好的方法是什么。这个过程大致是这样的:

首先,进行初始化:

  • 创建一个项目M。
  • 创建一个列表L,并把M添加到L里。

接着,进行循环:

  • 通过修改L中最后添加的项目来创建一个新项目。
  • 把这个新项目添加到L里。

举个简单的例子,假设我想创建一个列表的列表,其中第n个列表包含从1到n的数字。我可以用以下(有点傻)的方法。

  • 一开始M是[1],L=[[1]]。
  • 接下来,通过在[1]后面加2来修改它,得到新项目[1,2],然后把[1,2]添加到L中,L变成[[1],[1,2]]。
  • 然后,修改[1,2],在后面加3,得到新项目[1,2,3],再把[1,2,3]添加到L中,L变成[[1],[1,2],[1,2,3]]。
  • 接着,修改[1,2,3],在后面加4,得到新项目[1,2,3,4],再把[1,2,3,4]添加到L中,L变成[[1],[1,2],[1,2,3],[1,2,3,4]]。以此类推。

我尝试了几种方法,但大多数方法不仅修改了最后添加的项目,还修改了之前步骤中添加的项目。对于我感兴趣的具体问题,我确实找到了一种能正常工作的解决方案(至少在小范围内),但感觉不太优雅,我也不明白为什么它能工作,而其他方法却不行。我甚至不确定在处理大数据时它是否还能正常工作。我也不太确定能否把这种方法应用到类似的问题上。并不是说我不理解这个问题,因为我在其他编程语言中做过同样的事情,没有遇到问题。

所以我想知道更有经验的Python程序员会如何处理这个一般任务。

(我没有分享自己的代码,部分原因是我在这里是新手,还没弄明白如何在StackOverflow上输入代码,另外因为代码比较长,我不想要针对特定问题的帮助,而是想了解如何处理我上面描述的更一般的过程。)

5 个回答

2

试试这个:

M = [1]
L = [M]  
for _ in xrange(3):
    L += [L[-1] + [L[-1][-1] + 1]]

运行完上面的代码后,L 会包含 [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]。下面来解释一下:

  • 前两行代码只是给循环准备了一些初始值。
  • for 这一行说明我们想要循环多少次,这里是 3 次。我用 _ 作为循环变量,因为我们不关心它的值,只是想执行一定次数的循环。

现在进入有趣的部分;记住,在 Python 中,列表的负索引是从后往前数的,所以索引 -1 指向最后一个元素。

  • 这一行:L += … 更新列表,每次循环都会在最后添加一个新的子列表。
  • 这一行:[L[-1] + …] 通过取最后一个子列表并在末尾添加一个新元素来创建一个新的子列表。
  • 最后这一行:[L[-1][-1] + 1] 获取最后一个子列表中的最后一个元素,加一,然后返回一个只包含这个新值的列表,以便在前面的表达式后面连接。
3

这个内容是基于Haskell语言中的一个叫做iterate的功能。

iterate :: (a -> a) -> a -> [a]

iterate f x会返回一个无限长的列表,这个列表是把函数f反复应用到x上得到的结果:

iterate f x == [x, f x, f (f x), ...]

在Python中:

def iterate(f, x):
    while True:
        yield x
        x = f(x)

使用示例:

>>> import itertools.islice
>>> def take(n, iterable):
...     return list(islice(iterable, n))

>>> take(4, iterate(lambda x: x + [len(x) + 1], [1]))
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]

如果想要生成一个有限的列表,类型签名(这里为了清楚起见还是从Haskell开始)可以是infiniteFinitely :: (a -> Maybe a) -> a -> [a]

如果我们在Python中用list替代Maybe

from itertools import takewhile

def iterateFinitely(f, x):
    return map(lambda a: a[0], takewhile(len, iterate(lambda y: f(y[0]), [x])))

使用示例:

>>> list(iterateFinitely(lambda x: [x / 2] if x else [], 20))
[20, 10, 5, 2, 1, 0]

因为以一个假值结束的情况可能很常见,你也可以添加一个版本的这个函数来处理这种情况。

def iterateUntilFalsy(f, x):
    return iterateFinitely(lambda y: [f(y)] if y else [], x)

使用示例:

>>> list(iterateUntilFalsy(lambda x: x / 2, 20))
[20, 10, 5, 2, 1, 0]

>>> list(iterateUntilFalsy(lambda x: x[1:], [1,2,3,4]))
[[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []]
3

你可以这样做:

M=[1]
L=[M]

for e in range(5):
    li=L[-1][:]
    li.append(li[-1]+1)
    L.append(li)

或者更简洁一点:

for e in range(5):
    L.append(L[-1][:]+[L[-1][-1]+1])
3

我觉得最好的办法是用一个生成器。这样,你就不用去处理list.append、深拷贝列表或者那些麻烦的事情了。

def my_generator(max):
  for n in range(max+1):
    yield list(range(n+1))

接下来,你只需要把它变成列表:

>>> list(my_generator(5))
[[0], [0,1], [0,1,2], [0,1,2,3], [0,1,2,3,4], [0,1,2,3,4,5]]

这种方法也更灵活,如果你想让它变成一个无限生成器,只需把for循环换成while true就可以了。

11

当你把一个列表对象 M 加到另一个列表里时,其实你只是添加了一个引用; 如果你继续操作列表 M,那么你会发现其他引用也会显示出这些变化:

>>> M = []
>>> resultlist = []
>>> resultlist.append(M)
>>> M is resultlist[0]
True
>>> M.append(1)
>>> resultlist[0]
[1]
>>> M
[1]

注意 M is resultlist[0] 是真的; 它们是同一个对象

你应该添加 M 的一个副本

resultlist.append(M[:])

这里的整个切片([:] 表示从开始到结束切片)会创建一个新的列表,里面是 M 内容的浅拷贝。

从一个不断变化的起点 M 生成一个系列 L 的通用方法是使用一个生成器函数。你简单的 把下一个数字加到 M 的系列可以这样实现:

def growing_sequence():
    M = []
    counter = 0
    while True:
        M.append(counter)
        counter += 1
        yield M[:]

每次你迭代时,这个会生成越来越长的列表,根据需要

>>> gen = growing_sequence()
>>> next(gen)
[0]
>>> next(gen)
[0, 1]
>>> for i, lst in enumerate(gen):
...     print i, lst
...     if i == 2: break
...
0 [0, 1, 2]
1 [0, 1, 2, 3]
2 [0, 1, 2, 3, 4]

撰写回答