如何将 Python 列表列表的所有值设置为特定值?

kau*_*ray 4 python list

是否可以将 Python 列表列表中的所有值设置为 0,而无需一一遍历列表和值?

我有一个列表列表[[0, 2, 4, 5], [0, 2, 4, 5]],我想将其更改为[[0, 0, 0, 0], [0, 0, 0, 0]]. 有没有办法在不遍历所有值的情况下实现这一点,这会导致性能改进吗?由于这段代码将被大量执行,因此实现这一目标的最快方法是什么?

列表是就地修改还是完全替换也无关紧要。外链的长度会很大,而内链的长度会很小。

Mar*_*ers 6

不,没有办法避免循环,因为列表具有任意大小。您还希望避免以共享的单个嵌套列表结束,因此外部列表的乘法已结束。

以下是相当有效的并产生合理的结果:

[[0] * len(inner) for inner in outer]
Run Code Online (Sandbox Code Playgroud)

这将为 的任何长度产生正确的结果outer,即使嵌套列表的长度不同。

这也是不同场景中最快的方法,如下面的时间试验所示。首先要测试的设置:

>>> from timeit import timeit
>>> import random
>>> short_fixed = [[random.randint(0, 10) for _ in range(5)] for _ in range(10)]
>>> long_fixed = [[random.randint(0, 10) for _ in range(5)] for _ in range(1000000)]
>>> short_ranging = [[random.randint(0, 10) for _ in range(random.randrange(25))] for _ in range(10)]
>>> long_ranging = [[random.randint(0, 10) for _ in range(random.randrange(25))] for _ in range(1000000)]
Run Code Online (Sandbox Code Playgroud)

我正在运行 OS X 10.12.3 的 MacBook Pro(Retina,15 英寸,2015 年中)上使用 Python 3.6.1rc1 上的timeit模块进行测试

然后每个场景。Short fixed 是一个包含 10 个嵌套列表的列表,每个列表有 5 个元素长。测试时间是 100 万次重复的总和:

>>> timeit('list(map(lambda x:[0]*len(x),l))', 'from __main__ import short_fixed as l')
3.2795075319882017
>>> timeit('list(map(lambda x: list(repeat(0, len(x))), l))', 'from __main__ import short_fixed as l; from itertools import repeat')
6.128518687008182
>>> timeit('[[0] * len(inner) for inner in l]', 'from __main__ import short_fixed as l')
2.254983870021533
Run Code Online (Sandbox Code Playgroud)

长期固定测试 100 万个元素,10 次重复以保持等待可管理:

>>> timeit('list(map(lambda x:[0]*len(x),l))', 'from __main__ import long_fixed as l', number=10)
3.955955935991369
>>> timeit('list(map(lambda x: list(repeat(0, len(x))), l))', 'from __main__ import long_fixed as l; from itertools import repeat', number=10)
6.772360901988577
>>> timeit('[[0] * len(inner) for inner in l]', 'from __main__ import long_fixed as l', number=10)
3.302304288983578
Run Code Online (Sandbox Code Playgroud)

不同的列表大小介于 0 到 25 个元素之间。短名单:

>>> timeit('list(map(lambda x:[0]*len(x),l))', 'from __main__ import short_ranging as l')
3.155180420988472
>>> timeit('list(map(lambda x: list(repeat(0, len(x))), l))', 'from __main__ import short_ranging as l; from itertools import repeat')
6.213294043001952
>>> timeit('[[0] * len(inner) for inner in l]', 'from __main__ import short_ranging as l')
2.3255828430119436
Run Code Online (Sandbox Code Playgroud)

最后是 100 万个测距列表:

>>> timeit('list(map(lambda x: list(repeat(0, len(x))), l))', 'from __main__ import long_ranging as l; from itertools import repeat', number=10)
8.005676712986315
>>> timeit('list(map(lambda x: list(repeat(0, len(l[0]))), l))', 'from __main__ import long_ranging as l; from itertools import repeat', number=10)
8.49916388199199
>>> timeit('[[0] * len(inner) for inner in l]', 'from __main__ import long_ranging as l', number=10)
3.8087494230130687
Run Code Online (Sandbox Code Playgroud)

在所有情况下,显式循环都更快(最多 2 倍),因为它不必使用 lambda 函数。

如果您准备切换到 numpy 数组,那么该选项可以轻松解决所有问题。在数组中的所有(本机)值上广播乘以 0 将所有迭代移动到 C,根本不需要调用函数或执行 Python 字节码:

>>> import numpy
>>> short_fixed_np = numpy.array(short_fixed)
>>> long_fixed_np = numpy.array(long_fixed)
>>> short_ranging_np = numpy.array(short_ranging)
>>> long_ranging_np = numpy.array(long_ranging)
>>> timeit('l = next(copies); l *= 0', 'from __main__ import short_fixed_np as arr, numpy; copies = iter([numpy.copy(arr) for _ in range(10**6)])')
0.8011195910221431
>>> timeit('l = next(copies); l *= 0', 'from __main__ import long_fixed_np as arr, numpy; copies = iter([numpy.copy(arr) for _ in range(10)])', number=10)
0.04912398199667223
Run Code Online (Sandbox Code Playgroud)

(因为这种方法会就地更改对象,您需要为每个重复的测试创建足够的副本以更改唯一的数组,从而改变整个next(copies)舞蹈)。

充分利用 numpy 数组也意味着您只能将它们实际用于固定长度的子列表。对于可变长度的子列表,您必须使用 object 类型的单维数组(意味着它们仅用于引用 Python 列表),此时您也不能再将乘法广播到所有数字元素。

考虑到在这种情况下您必须重新调整整个项目才能利用 numpy 数组。如果您需要大量访问此类数组中的单个值,请考虑到这会变慢,因为访问单个值需要每次在 Python 对象中装箱 C 本机值。