Python中最快的两个列表合并方式是什么?

8

让我们创建2个列表

l1 = [1, 2, 3]
l2 = [a, b, c, d, e, f, g...]

结果:

list = [1, a, 2, b, 3, c, d, e, f, g...] 

不能使用 zip() 函数,因为它会把结果缩短到最小的list。我需要输出时也是一个list,而不是一个iterable


最快、最优雅或最Pythonic? - Ray Toal
7个回答

10
>>> l1 = [1,2,3]
>>> l2 = ['a','b','c','d','e','f','g']
>>> [i for i in itertools.chain(*itertools.izip_longest(l1,l2)) if i is not None]
[1, 'a', 2, 'b', 3, 'c', 'd', 'e', 'f', 'g']

为了允许在列表中包含None值,您可以使用以下修改:

>>> from itertools import chain, izip_longest
>>> l1 = [1, None, 2, 3]
>>> l2 = ['a','b','c','d','e','f','g']
>>> sentinel = object()
>>> [i
     for i in chain(*izip_longest(l1, l2, fillvalue=sentinel))
     if i is not sentinel]
[1, 'a', None, 'b', 2, 'c', 3, 'd', 'e', 'f', 'g']

不需要在那里使用双括号。itertools.chain((...))可以简单地写作itertools.chain(...)。哦,而且您可能希望将整个内容封装在list(...)中。 - arshajii

7

Another possibility...

[y for x in izip_longest(l1, l2) for y in x if y is not None]

(当然,在导入itertools的izip_longest之后)

即使不是最具描述性的,这也是首选。 :) - jathanism

2

最简单的方法是使用在itertools文档中提供的轮询配方

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))

可以像这样使用:

>>> l1 = [1,2,3]
>>> l2 = ["a", "b", "c", "d", "e", "f", "g"]
>>> list(roundrobin(l1, l2))
[1, 'a', 2, 'b', 3, 'c', 'd', 'e', 'f', 'g']

请注意,2.x版本需要稍微不同版本的roundrobin,该版本在2.x文档中提供。
这也避免了zip_longest()方法存在的问题,即列表可能包含None而不被剥离。

我不确定是否应该称它为最简单的,考虑到你必须定义整个新函数使其工作。 - asmeurer
1
@asmeurer 既然这是一个已知的配方,包含它并不是什么困难。而且,正如我在底部提到的那样,它还有一个好处——如果列表包含“None”,则“zip_longest()”方法将无法按预期工作。从使用角度来看,该函数清晰简单。 - Gareth Latty
@Lattyware 我对izip_longest的解决方案增加了一种简单的修复None问题的方法。目前我能看到唯一的优点就是速度快,不需要潜在迭代和丢弃大量sentinels。 - jamylak
@jamylak 我认为这个解决方案更清晰、更易读,这本身就有价值。 - Gareth Latty

1
minLen = len(l1) if len(l1) < len(l2) else len(l2)
for i in range(0, minLen):
  list[2*i] = l1[i]
  list[2*i+1] = l2[i]
list[i*2+2:] = l1[i+1:] if len(l1) > len(l2) else l2[i+1:]

这不是一种简短的方法,但它可以去除不必要的依赖。

更新: 这里有另一种方法,由@jsvk建议。

mixed = []
for i in range( len(min(l1, l2)) ):
  mixed.append(l1[i])
  mixed.append(l2[i])
list += max(l1, l2)[i+1:]

你所指的“不必要的依赖”是什么?到目前为止,其他任何解决方案中表达的唯一其他依赖项是itertools,它是Python标准库的一部分。 - inspectorG4dget
是的,但它仍然需要由解释器加载。 我并不是说其他解决方案不好,它们可能更适合大多数情况,但我只是发布了另一种方法。 - Ionut Hulub

0
如果你需要将输出变成一个列表,而不是元组的列表,请尝试这个方法。
Out=[]
[(Out.extend(i) for i in (itertools.izip_longest(l1,l2))]
Out=filter(None, Out)

0

我不声称这是最好的方法,但我只是想指出可以使用zip:

b = zip(l1, l2)
a = []
[a.extend(i) for i in b]
a.extend(max([l1, l2], key=len)[len(b):])

>>> a
[1, 'a', 2, 'b', 3, 'c', 'd', 'e', 'f', 'g']

除非进行修改,否则它在Python 3中无法正常工作,因为len(zip)在那里没有定义。 - asmeurer
1
使用列表推导式来产生副作用也不是一个好主意。应该使用 itertools.chain() 代替。 - Gareth Latty

0
>>> a = [1, 2, 3]
>>> b = list("abcdefg")
>>> [x for e in zip(a, b) for x in e] + b[len(a):]
[1, 'a', 2, 'b', 3, 'c', 'd', 'e', 'f', 'g']

这里假设“b”比“a”更长,虽然可以通过编写一些代码来解决这个问题。 - Gareth Latty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接