【问题标题】:How to set all values of a Python list of lists to a specific value?如何将 Python 列表列表的所有值设置为特定值?
【发布时间】:2017-03-20 18:48:07
【问题描述】:

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

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

列表是否被原地修改或完全替换也无关紧要。外部列表的长度会很大,而内部列表的长度会很小。

【问题讨论】:

  • 是否总是 2 个列表,每个列表 4 个元素?
  • 如果您的列表是 numpy 数组,您可以执行 yourlist = np.zeros_like(yourlist),但这并不能回答所提出的问题,除非您想包括创建 numpy 数组和制作列表的开销最后。
  • @PeterWood 不,以上只是一个例子
  • @KaustabhaRay:然后说那个。你能edit你的问题吗?a)提到列表是否被原地更改或完全替换并不重要,b)你想为此找到最快的方法。那么我们就知道计时结果了,而不是争论什么方法可以让你不循环。
  • @KaustabhaRay:另外,您能否告诉我们列表将有多大(内部和外部列表的大致数字)。有些方法对于短列表可能更快,而对于更大的列表则迅速失地。

标签: python list


【解决方案1】:

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

以下是相当有效的,并产生一个理智的结果:

[[0] * len(inner) for inner in outer]

这将为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)]

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

然后是每个场景。短固定是 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

长期固定测试 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

可变的列表大小介于 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

最后是 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

在所有情况下,显式循环都更快(高达 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

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

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

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

【讨论】:

  • 我很确定如果内部列表的长度不同,numpy 解决方案会失败(就像我对zero_like 的建议一样)。问题是np.array 只创建一个列表数组(dtype=object),如果内部列表的长度不相等,list*0 = []。这意味着short_fixedlong_fixed 可以,但是short_ranging 和'long_ranging` 没有意义。
  • @StefanS:啊,确实。我会调整一下,除了固定列表之外的任何东西都使用 numpy 数组是没用的。
【解决方案2】:

没有显式循环,这里是使用itertools.repeat()map() 的函数式方法:

In [6]: lst = list(map(lambda x: list(repeat(0, len(x))), lst))
Out[6]: [[0, 0, 0, 0], [0, 0, 0, 0]]

或者,如果您的子列表长度相同,您可以只使用两个repeat()

In [24]: lst = list(repeat(list(repeat(0, len(lst[0]))), len(lst)))
Out[24]: [[0, 0, 0, 0], [0, 0, 0, 0]]

请注意,此方法使用循环来创建重复对象并将生成器转换为列表。这意味着没有循环就无法创建像这样的单独对象。

另一种就地更改项目的方法是使用 Numpy。您可以通过简单地乘以 0 来做到这一点:

In [18]: import numpy as np

In [19]: lst = np.array(lst)

In [21]: lst *= 0

In [22]: lst
Out[22]: 
array([[0, 0, 0, 0],
       [0, 0, 0, 0]])

【讨论】:

  • 仍然使用循环,但这些循环由list 实现执行。由于需要lambda,您还需要为每个函数调用支付帧推送性能损失。
  • @MartijnPieters 绝对是,这只是按照 OP 的要求没有显式循环
  • 这不会修改原始列表
  • 如果任何内部列表的长度不匹配,您的 numpy 解决方案将不起作用(将返回空列表)。它恰好对给定的样本有利。
  • @DavidArenburg:完成,正如预期的那样,numpy 的速度要快得多。只要就地更改没问题,以及重新调整项目的其余部分以使用 numpy 数组,这显然是更好的选择。
【解决方案3】:

据我所知,避免循环是不可能的,但你说明确循环我想你想要一种没有for循环的方法。

所以你可以试试这个:

print map(lambda x:[0]*len(x),l)

Python 3.x,应该是:

print (list(map(lambda x:[0]*len(x),l)))

Map:

将函数应用于可迭代的每个项目并返回一个列表 结果。

【讨论】:

  • 这是 Python 2 特有的; map() 在这里循环。 lambda 调用会减慢速度,因为每次迭代都必须创建一个新的框架对象并将其推送到堆栈中。对于 Python 3,您必须添加一个 list() 调用。
  • @MartijnPieters 当然,我认为避免循环是不可能的,所以我想也许 OP 只是想要一种不使用for 循环的方法,我会更新这个。
  • 事实证明,这仅比 Kasra 的解决方案快一点,但并没有快多少。函数在这里调用kill performance。
【解决方案4】:

您可以使用以下方法更新您的列表(没有明确的 for 循环):

list_of_lists = [[0] * 4] * 2

让我们试试吧:

>>> [[0] * 4] *2 
[[0, 0, 0, 0], [0, 0, 0, 0]]

【讨论】:

  • * 2 列出的是同一个列表吗?
  • “相同”是什么意思?它将产生 [[0,0,0,0],[0,0,0,0]]。
  • 是的,但是如果您随后修改一个子列表,它会同时修改两个吗?即如果A = [0, 0, 0, 0] 你现在有[A, A] 吗?
  • @CaptainTrunky:是的,它会为一个共享​​>列表对象生成两个引用。尝试设置result[0][0] = 1
  • 啊,是的,没错。
猜你喜欢
  • 2012-07-08
  • 1970-01-01
  • 1970-01-01
  • 2017-10-24
  • 2021-11-05
  • 2022-12-31
  • 1970-01-01
  • 2022-12-01
  • 2021-12-07
相关资源
最近更新 更多