用Pythonic方式以交替方式合并两个列表的方法?


88

我有两个列表,其中第一个肯定比第二个多包含一个项目。我想知道最Python方式创建一个新列表,该列表的偶数索引值来自第一个列表,其奇数索引值来自第二个列表。

# 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

还有什么可以实现的呢?最Python化的方法是什么?



不能重复!上面链接的文章中接受的答案会生成一个元组列表,而不是一个合并列表。
保罗·萨西克

@Paul:是的,被接受的答案并未给出完整的解决方案。阅读评论和其他答案。问题基本上是相同的,其他解决方案也可以在这里应用。
菲利克斯·克林

3
@Felix:我谨表示不同意。的确,问题在同一街区,但并非真正重复。作为模糊的证明,请看一下这里可能存在的答案,并与另一个问题进行比较。
保罗·萨西克

Answers:


110

这是切片的一种方法:

>>> 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']

3
谢谢,邓肯。我没有意识到切片时可以指定一个步骤。我喜欢这种方法的原因是它读起来很自然。1.列出正确的长度。2.用list1的内容填充偶数索引。3.用list2的内容填充奇数索引。列表长度不同的事实在这种情况下不成问题!
davidchambers,2010年

2
我认为它只能当LEN(列表1) -为0或1 LEN(列表2)
XAN

1
如果列表的长度合适,则可以使用,否则,原始问题不会指定期望的答案。可以很容易地对其进行修改,以处理最合理的情况:例如,如果您想忽略多余的元素,只需在开始之前将较长的列表切掉;如果您想让多余的元素与None交错,则只需确保用更多的None初始化结果即可;如果您只想在最后添加额外的元素,则可以忽略它们,然后附加它们。
邓肯2010年

1
我也不清楚。我要说明的是,与列出的许多解决方案不同,邓肯的解决方案并不复杂,因为列表的长度不相等。当然,它仅适用于有限范围的情况,但是我更喜欢在这种情况下可以使用的非常优雅的解决方案,而不是对任何两个列表都可以使用的不太优雅的解决方案。
davidchambers,2010年

1
您可以使用(2 * len(list1)-1)代替(len(list1)+ len(list2)),我也更喜欢[0 :: 2]而不是[:: 2]。
英国勋爵

50

itertools文档中有一个配方:

from itertools import cycle, islice

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))

编辑:

对于大于3的python版本:

from itertools import cycle, islice

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))

我发现这种方式比需要的复杂。下面有一个更好的选择zip_longest
Dubslow

@Dubslow对于这种特殊情况,是的,除非您碰巧已经可以使用它,否则这可能是过大的(正如我在其他评论中提到的那样)。但是,在其他情况下,它可能具有一些优势。这个食谱当然不是为解决这个问题而设计的,它只是解决了它。
David Z

1
仅供参考,您应该使用itertools 文档中的配方,因为它.next()不再起作用。
约翰·w。

1
@johnw。一个要用__next__。它没有写在文档中,所以我建议对答案进行编辑。
Marine Galantin

@Marine我有点希望您刚刚更改了现有代码示例,但我可以自己修复此问题。感谢您的贡献!
David Z

31

这应该做您想要的:

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

我真的很喜欢你的最初答案。尽管它不能完美地解决问题,但这是合并两个相同长度列表的一种优雅方法。我建议在当前答案中保留它,并加长注意事项。
保罗·萨西克

1
如果将list1改为['f','o','o','d'],则其最后一项('d')将不会出现在结果列表中(考虑到问题的具体情况,这是完全可以的)。这是一个优雅的解决方案!
davidchambers,2010年

1
@Mark是(我赞成),只是指出了差异(如果其他人想要不同的行为,还有一些限制)
cobbal

4
+1可以解决上述问题,并且也可以简单地做到这一点:-)我想这样的事情是可能的。老实说,我认为此roundrobin功能对于这种情况有些过分了。
大卫Z

1
要使用任何大小的列表,您只需将迭代器中剩余的内容附加到结果中:list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
panda-34

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

我认为这是最Python化的方式。


3
为什么这不是公认的答案?这是最短和最Python的语言,可以使用不同的列表长度!
Jairo Vadillo

5
方法名称是zip_longest而不是izip_longest
Jairo Vadillo

1
问题是zip_longest的默认fillin值可能会覆盖None*假定位于列表中的。我将编辑调整后的版本以解决此问题
Dubslow

注意:这将导致问题如果列表中包含有值的元素False,或将只事情变得评估作为Falseif-expression,例如像0一个空列表。可以通过(部分)避免这种情况:[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]。当然,如果列表中包含None需要保留的元素,那么这仍然行不通。在这种情况下,您需要更改Dubslow已经建议的fillvalue参数zip_longest
der_herr_g

None这个问题似乎已经消失了,至少从Python 3.7.6起(对于较旧的版本我不知道)。如果alt_chain定义为def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue)),则list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))正确返回[0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99]
paime

17

如果没有itertools,并假设l1比l2长1个项目:

>>> sum(zip(l1, l2+[0]), ())[:-1]
('f', 'hello', 'o', 'world', 'o')

使用itertools并假设列表不包含None:

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')

这是我最喜欢的答案。简明扼要。
mbomb007 '17

@ anishtain4 zip从列表中将成对的元素作为元组[(l1[0], l2[0]), (l1[1], l2[1]), ...]sum将元组连接在一起:(l1[0], l2[0]) + (l1[1], l2[1]) + ...产生交错列表。单行的其余部分只是对l1进行填充,并添加额外的元素以使zip工作并切片直到-1以摆脱该填充。
Zart

izip_longest(自python 3起为zip_longest)不需要+ [0]填充,当列表的长度不匹配时,它隐式填充None,而filter(None, ...(可以bool替代使用或None.__ne__)删除错误值,包括0,无和空字符串,因此第二种表达方式并不严格等同于第一种表达方式。
Zart

问题是您是如何sum做到的?那里第二个论点的作用是什么?在文档中,第二个参数是start
anishtain4

start的默认值为0,并且您不能执行0+(some,tuple),因此start更改为空tuple。
Zart

13

我知道问题要问的是两个清单,其中一个清单比另一个清单要多,但我想我会把这个清单发给其他可能找到这个问题的清单。

这是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'] 

6

如果两个列表的长度相等,则可以执行以下操作:

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

由于第一个列表还有一个元素,因此可以事后添加:

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

2
^这应该是答案,在过去的十年中python变得更加pythonic

5

这是一个可以做到的:

list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]


2
这可以正常工作,但是让我感到不雅,因为它为达到如此简单的目的已经做了很多事情。我并不是说这种方法效率低下,只是因为它不是特别容易阅读。
davidchambers,2010年

2
def combine(list1, list2):
    lst = []
    len1 = len(list1)
    len2 = len(list2)

    for index in range( max(len1, len2) ):
        if index+1 <= len1:
            lst += [list1[index]]

        if index+1 <= len2:
            lst += [list2[index]]

    return lst

使用可变的默认参数时要非常小心。这只会在第一次调用时返回正确的答案,因为在此之后的每个调用中都会重用lst。最好将其写为lst = None ...如果lst为None:lst = [],尽管我看不出有什么迫切的理由要选择这种方法而不是这里列出的其他方法。
davidchambers,2010年

lst是在函数中定义的,局部变量也是。潜在的问题是,即使您使用其他列表调用该函数,每次使用该函数时都将重用list1和list2。见docs.python.org/tutorial/...
blokeley

1
@blokeley:错误,如果将其组合使用,将被重用(list1 = [...],list2 = [...])
killown 2010年

首次发布此解决方案时,其第一行显示为def combine(list1, list2, lst=[]):,因此是我的评论。但是,到我提交评论时,killown已经进行了必要的更改。
davidchambers,2010年

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]

1

我的看法:

a = "hlowrd"
b = "el ol"

def func(xs, ys):
    ys = iter(ys)
    for x in xs:
        yield x
        yield ys.next()

print [x for x in func(a, b)]

1

这是一个使用列表推导,不带其他库的衬板:

list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]

如果允许通过副作用更改初始列表1,则这是另一种方法:

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]

1

可能会晚一点再买另一条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']

1
from itertools import chain
list(chain(*zip('abc', 'def')))  # Note: this only works for lists of equal length
['a', 'd', 'b', 'e', 'c', 'f']

0

停在最短的位置:

def interlace(*iters, next = next) -> collections.Iterable:
    """
    interlace(i1, i2, ..., in) -> (
        i1-0, i2-0, ..., in-0,
        i1-1, i2-1, ..., in-1,
        .
        .
        .
        i1-n, i2-n, ..., in-n,
    )
    """
    return map(next, cycle([iter(x) for x in iters]))

当然,解决next / __ next__方法可能更快。


0

这很讨厌,但是不管列表的大小如何都可以:

list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]

0

多个单线从另一个问题答案中得到启发:

import itertools

list(itertools.chain.from_iterable(itertools.izip_longest(list1, list2, fillvalue=object)))[:-1]

[i for l in itertools.izip_longest(list1, list2, fillvalue=object) for i in l if i is not object]

[item for sublist in map(None, list1, list2) for item in sublist][:-1]


0

一种功能不变的替代方法(Python 3):

from itertools import zip_longest
from functools import reduce

reduce(lambda lst, zipped: [*lst, *zipped] if zipped[1] != None else [*lst, zipped[0]], zip_longest(list1, list2),[])

-1

我会做简单的:

chain.from_iterable( izip( list1, list2 ) )

它将提供一个迭代器,而无需创建任何其他存储需求。


1
这确实很简单,但仅适用于相同长度的列表!
Jochen Ritzel

您可以chain.from_iterable(izip(list1, list2), list1[len(list2):])针对此处提出的特定问题进行修复... list1应该是更长的。
Jochen Ritzel 2010年

是的,但我宁愿想出一个对任意长度的容器都适用的解决方案,或者对上面提出的解决方案有所帮助。
小麦色

-2

我太老了,无法接受列表理解,所以:

import operator
list3 = reduce(operator.add, zip(list1, list2))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.