Python中两个列表交替合并的Pythonic方式是什么?(即交错插入、交替组合等)

134

我有两个列表,第一个列表一定比第二个多一项。我想知道创建一个新列表的最Pythonic方法,其中偶数索引值来自第一个列表,奇数索引值来自第二个列表。

# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']

# desired output
['f', 'hello', 'o', 'world', 'o']
这个可以运行,但不太美观:
list3 = []
while True:
    try:
        list3.append(list1.pop(0))
        list3.append(list2.pop(0))
    except IndexError:
        break
如何以其它方式实现?什么是最Pythonic的方法?
如果你需要处理长度不匹配的列表(例如第二个列表比第一个长,或者第一个比第二个多一个元素以上),一些解决方案在这里将起作用,而其他解决方案则需要进行调整。有关更具体的答案,请参见如何交错两个不同长度的列表并将多余的元素留在末尾?,或如何优雅地交错两个长度不均匀的列表?以尝试均匀插入元素,或在每个第N个元素之后向Python列表中插入元素来处理每个“添加”元素之前应该出现特定数量的元素的情况。

@Paul:是的,被采纳的答案并没有给出完整的解决方案。请阅读评论和其他答案。问题基本上是相同的,其他解决方案也可以应用于这里。 - Felix Kling
3
@Felix:我非常尊重地不同意。的确,这些问题在同一个领域,但并不是真正的重复。作为模糊的证据,请看看这里的潜在答案,并与其他问题进行比较。 - Paul Sasik
请查看以下内容:https://dev59.com/pmsz5IYBdhLWcg3w-89v - wordsforthewise
26个回答

167

以下是一种使用切片的方法:

>>> list1 = ['f', 'o', 'o']
>>> list2 = ['hello', 'world']
>>> result = [None]*(len(list1)+len(list2))
>>> result[::2] = list1
>>> result[1::2] = list2
>>> result
['f', 'hello', 'o', 'world', 'o']

6
谢谢,邓肯。我之前不知道在Python中切片时可以指定步长。我喜欢这种方法的原因是它读起来非常自然。
  1. 创建一个正确长度的列表。
  2. 用list1的内容填充偶数索引。
  3. 用list2的内容填充奇数索引。
这两个列表长度不同并不是问题!
- davidchambers
3
我认为只有当 list1 的长度减去 list2 的长度等于0或1时,它才能正常工作。 - xan
1
如果列表长度合适,则它可以正常工作,否则原始问题并没有指定期望的答案。它可以很容易地修改以处理大多数合理的情况:例如,如果您想要忽略额外的元素,请在开始之前缩短较长的列表;如果您希望将额外的元素与None交错,则请确保结果初始化为更多的None;如果您想要添加额外的元素,则请按照忽略它们的方式进行,然后将它们附加到末尾。 - Duncan
1
我也不是很清楚。我想表达的意思是,与许多其他解决方案不同,邓肯的解决方案并没有因为列表长度不相等而变得复杂。当然,它只适用于有限的情况,但我更喜欢在这种情况下能够工作的非常优雅的解决方案,而不是适用于任何两个列表的不太优雅的解决方案。 - davidchambers
1
这不是一个很Pythonic的解决方案:提前创建列表对于大输入来说可能非常浪费,而且我认为使用输入迭代器的总长度也不够Pythonic。下面有更清晰的解决方案,可以生成惰性生成器(当然可以根据需要轻松地强制转换为列表)。 - Dubslow
显示剩余5条评论

55

itertools文档中有一个配方可以做到这一点(注意:适用于Python 3):

from itertools import cycle, islice

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

2
我认为这种方式比必要的复杂。下面有一个更好的选项,使用zip_longest - Dubslow
@Dubslow 对于这种特殊情况,是的,这可能有些过度(正如我在其他评论中提到的那样),除非你已经可以访问它。但在其他情况下,它可能具有一些优势。这个方法显然不是为了解决这个问题而设计的,只是碰巧可以解决它。 - David Z
1
你应该使用itertools文档中的配方,因为.next()已经不再起作用了。 - john w.
1
@johnw. 必须使用__next__。这在文档中没有说明,所以我建议对答案进行编辑。 - Marine Galantin
@Marine 我更希望你只是修改了现有的代码示例,但我可以自己解决。感谢你的贡献! - David Z
Python 版本大于 3 让我笑了... 我总是渴望一睹 Python 4 代码的样子。 - Eric

52
import itertools
print([x for x in itertools.chain.from_iterable(itertools.zip_longest(list1,list2)) if x])

我认为这是最符合Python风格的方法。


3
为什么这个答案没有被采纳?这是最短、最符合Python风格的,并且适用于不同长度的列表! - Jairo Vadillo
9
方法名为zip_longest而非izip_longest。 - Jairo Vadillo
2
问题在于zip_longest的默认填充值可能会覆盖列表中本来就存在的“None”值。我将编辑一个经过调整的版本来解决这个问题。 - Dubslow
3
注意:如果列表中包含值为 False 或者只能通过 if 表达式求值为 False 的元素,比如一个 0 或者一个空列表,则会引起问题。这可以通过以下方式(部分)避免:[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]。当然,如果列表包含需要保留的 None 元素,则仍然无法解决该问题。在这种情况下,需要像 Dubslow 建议的那样更改 zip_longestfillvalue 参数。 - der_herr_g
@paime:问题不会出现在您的代码中,因为您没有过滤结果(在列表推导式结尾处没有 if x)。您最终面临另一个问题,即将大量的 99 插入到数据中,这些数据在任何输入中都不存在。 - ShadowRanger
显示剩余2条评论

33

在Python 2中,这应该可以实现你想要的功能:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']

我真的很喜欢你的初步回答。虽然它并没有完全回答问题,但是它是一种优雅的方式来合并两个长度相同的列表。我建议在你目前的回答中保留它,以及长度的限制。 - Paul Sasik
1
如果list1改为['f', 'o', 'o', 'd'],那么它的最后一个元素('d')将不会出现在结果列表中(考虑到问题的具体情况,这完全没问题)。这是一种优雅的解决方案! - davidchambers
1
@Mark 是的,我已经点赞了,只是想指出其中的差异(以及如果其他人想要不同的行为时的限制)。 - cobbal
4
+1表示解决了所述问题,并且做得很简单 :-) 我想这种情况下可能有类似的解决方法。老实说,我觉得“roundrobin”函数在这种情况下有点过头了。 - David Z
1
为了处理任意大小的列表,您可以将迭代器中剩余的部分附加到结果中:list(itertools.chain(map(next, itertools.cycle(iters)), *iters)) - panda-34
显示剩余7条评论

21

假设l1比l2多一个元素,不使用itertools:

>>> sum(zip(l1, l2+[0]), ())[:-1]
('f', 'hello', 'o', 'world', 'o')
在Python 2中,使用itertools并假设列表不包含None:
>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')

这是我最喜欢的答案。它非常简洁。 - mbomb007
@anishtain4 zip从列表中以元组的形式获取元素对,如[(l1[0], l2[0]), (l1[1], l2[1]), ...]sum将元组串联在一起:(l1[0], l2[0]) + (l1[1], l2[1]) + ... 从而得到交错的列表。这一行代码的其余部分只是为了让zip函数正常工作,并通过切片去掉填充的额外元素。 - Zart
izip_longest(自 Python 3 起改为 zip_longest)不需要使用+ [0]填充,当列表的长度不匹配时,它会隐式地填充 None。而filter(None,...(也可以使用boolNone.__ne__),则会删除 false 值,包括 0、None 和空字符串,因此第二个表达式与第一个表达式并不严格等价。 - Zart
问题是你是如何让 sum 做到那样的?第二个参数在那里扮演什么角色?在文档中,第二个参数是 start - anishtain4
起始值的默认值为0,但您无法执行0 +(某些,元组),因此起始值更改为空元组。 - Zart
不使用填充和其移除:sum(zip(l2, l1[1:]), (l1[0],)) - Kelly Bundy

19
如果两个列表长度相等,您可以执行以下操作:
[x for y in zip(list1, list2) for x in y]

由于第一个列表有一个额外的元素,您可以事后添加它:

[x for y in zip(list1, list2) for x in y] + [list1[-1]]

编辑:为了说明第一个列表推导式中发生的事情,以下是将其拼写为嵌套的for循环的方式:

result = []
for y in zip(list1, list2): # y is is a 2-tuple, containining one element from each list
    for x in y: # iterate over the 2-tuple
        result.append(x) # append each element individually

5
在过去的10年中,Python变得更加“Pythonic”,这应该就是答案。 - Tian
非常符合 Python 风格,但是让我这个小脑袋有点吃不消。我该如何解析它? - bard

14

我知道这个问题是关于两个列表,其中一个比另一个多一个项目,但我觉得我会把这个答案放在这里,供其他可能遇到同样问题的人参考。

这里是Duncan的解决方案,适用于不同大小的两个列表。

list1 = ['f', 'o', 'o', 'b', 'a', 'r']
list2 = ['hello', 'world']
num = min(len(list1), len(list2))
result = [None]*(num*2)
result[::2] = list1[:num]
result[1::2] = list2[:num]
result.extend(list1[num:])
result.extend(list2[num:])
result

这将输出:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 

7
这里有一个一行代码解决它的方法: list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1] 其中,list1和list2是两个相关联的列表,以上代码将它们“缝合”在一起,并将结果存储在list3中。

3
这个方法虽然能正常工作,但我觉得它不太优雅,因为它为了实现一个简单的目标而做了很多事情。我并不是说这种方法效率低下,只是它不太容易阅读。 - davidchambers

2
这个是基于Carlos Valiente上面的贡献,增加了一个选项来交替多个项目组,并确保所有项目都出现在输出中:
A=["a","b","c","d"]
B=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

def cyclemix(xs, ys, n=1):
    for p in range(0,int((len(ys)+len(xs))/n)):
        for g in range(0,min(len(ys),n)):
            yield ys[0]
            ys.append(ys.pop(0))
        for g in range(0,min(len(xs),n)):
            yield xs[0]
            xs.append(xs.pop(0))

print [x for x in cyclemix(A, B, 3)]

这将通过每组3个值,交错合并列表A和B:

['a', 'b', 'c', 1, 2, 3, 'd', 'a', 'b', 4, 5, 6, 'c', 'd', 'a', 7, 8, 9, 'b', 'c', 'd', 10, 11, 12, 'a', 'b', 'c', 13, 14, 15]

2

可能有点晚,但这是另一个Python单行代码。当两个列表大小相等或不相等时都可以使用。值得注意的一件事是它会修改a和b。如果这是个问题,你需要使用其他解决方案。

a = ['f', 'o', 'o']
b = ['hello', 'world']
sum([[a.pop(0), b.pop(0)] for i in range(min(len(a), len(b)))],[])+a+b
['f', 'hello', 'o', 'world', 'o']

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