23

连接两个列表的最有效方法是什么list_a以及list_b何时:

  • list_b物品必须放在list_a物品之前
  • 结果必须放在list_a

我想到了 4 种可能性:

# 1
list_a = list_b + list_a

# 2
for item in list_b:
    list_a.insert(0, item)

# 3
for item in self.list_a:
    list_b.append(item)
list_a = list_b

# 4
list_a[0:0] = list_b

谢谢!

4

6 回答 6

32

这是BigYellowCactus 答案中使用的时间如何随着列表长度的增加而发展的图表。纵轴是初始化两个列表并在另一个前面插入一个列表所需的时间,以 usec 为单位。水平轴是列表中的项目数。

可能性的渐近行为

t1

list_a = list_b + list_a

t2

for item in list_b:
    list_a.insert(0, item)

t3

for item in list_a:
    list_b.append(item)
list_a = list_b

t4

list_a[0:0] = list_b
于 2012-08-23T09:34:36.603 回答
8

鉴于

list_a = list_b + list_a

适用于您的目的,因此您实际上并不需要list_a对象本身来存储所有数据list_a- 您只需要调用 list_a它(即,您没有或不关心您拥有的任何其他变量浮动可能指的是同一个列表)。

如果您也不关心它是否完全是一个列表,而只是关心它是可迭代的,那么您可以使用itertools.chain

list_a = itertools.chain(list_b, list_a)

如果您确实关心某些列表内容,则可以构建类似于列表的类似事物类型chain-例如:

class ListChain(list):
    def __init__(self, *lists):
        self._lists = lists

    def __iter__(self):
        return itertools.chain.from_iterable(self._lists)

    def __len__(self):
        return sum(len(l) for l in self._lists)

    def append(self, item):
        self._lists[-1].append(item)

    def extend(self, iterable):
        self._lists.append(list(iterable))

    def __getitem__(self, item):
       for l in self._lists:
           if item < len(l):
              return l[item]
           item -= len(l)
       else:
          raise IndexError

等等。这需要付出很多努力(可能超过其价值)才能在所有情况下工作 - 例如,处理切片和负索引浮现在脑海中。但是对于非常简单的情况,这种方法可以避免大量复制列表内容。

于 2012-08-23T10:12:49.453 回答
6

您可以将 list_b 分配给一个切片,该切片恰好是空的,但位于 list_a 的开头:

list_a[0:0] = list_b

这是在任何位置将列表插入另一个列表的最快方法。

于 2012-08-23T09:00:33.967 回答
5

尝试这个:

list_a[0:0] = list_b
于 2012-08-23T08:59:01.977 回答
4

为什么不只是timeit

import timeit

create_data = """\
list_a = range(10)
list_b = range(10)
"""

t1 = timeit.Timer(stmt=create_data + """\
list_a = list_b + list_a
""")

t2 = timeit.Timer(create_data + """\
for item in list_b:
    list_a.insert(0, item)
""")

t3 = timeit.Timer(create_data + """\
for item in list_a:
    list_b.append(item)
list_a = list_b
""")

t4 = timeit.Timer(create_data + """\
list_a[0:0] = list_b
""")

for i, t in enumerate([t1,t2,t3,t4]):
    print i, "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)

结果:

0 0.73 微秒/遍
1 2.79 微秒/遍
2 1.66 微秒/遍
3 0.77 微秒/遍

于 2012-08-23T09:03:06.760 回答
4

在此处输入图像描述

itertools.chain只需制作一个生成器,因此如果您可以使用生成器而不是列表来摆脱困境,那么生成的时间是恒定的,但是您在访问每个元素时会付出代价。否则list_a[0:0] = list_b大约快 6 倍list_a = list_b + list_a

我认为这list_a = list_b + list_a是最易读的选择,而且已经相当快了。

您提到的append()for循环中使用的两种方法非常慢,所以我没有费心将它们包括在内。


[Clang 11.0.0 (clang-1100.0.33.8)] on darwin使用以下代码在 1.6 GHz 双核 Intel Core i5 和 16 GB 2133 MHz LPDDR3 RAM 上运行Python 3.7.5 :

from timeit import timeit
import random
import matplotlib.pyplot as plt

num_data_points = 1000
step = 10
methods = [
    # ordered from slowest to fastest to make the key easier to read
    # """for item in list_a: list_b.append(item); list_a = list_b""",
    # """for item in list_b: list_a.insert(0, item)""",
    # "list_a = list(itertools.chain(list_b, list_a))",
    "list_a = list_b + list_a",
    "list_a[0:0] = list_b",
    "list_a = itertools.chain(list_b, list_a)",
]

x = list(range(0, num_data_points * step, step))
y = [[] for _ in methods]
for i in x:
    list_a = list(range(i))
    list_b = list(range(i))
    random.shuffle(list_a)
    random.shuffle(list_b)
    setup = f"list_a = {list_a}; list_b = {list_b}"
    for method_index, method in enumerate(methods):
        y[method_index].append(timeit(method, setup=setup, number=30))
    print(i, "out of", num_data_points * step)

ax = plt.axes()
for method_index, method in enumerate(methods):
    ax.plot(x, y[method_index], label=method)
ax.set(xlabel="number of elements in both lists", ylabel="time (s) (lower is better)")
ax.legend()
plt.show()
于 2019-11-17T07:43:02.943 回答