繁体   English   中英

以交替方式组合(交错、交错、交织)两个列表的 Pythonic 方式?

[英]Pythonic way to combine (interleave, interlace, intertwine) two lists in an alternating fashion?

我有两个列表,其中第一个保证比第二个多包含一个项目 我想知道创建一个新列表的最 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 的方法?


如果您需要处理长度不匹配的列表(例如,第二个列表更长,或者第一个列表的元素比第二个多),这里的一些解决方案可以工作,而其他解决方案则需要调整。 有关更具体的答案,请参阅How to interleave two lists of different length? 将多余的元素留在最后,或者如何在 python 中优雅地交错两个长度不均匀的列表? 尝试均匀地散布元素。

这是通过切片来实现的一种方法:

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

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))
import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

我认为这是最 Pythonic 的方式。

在 Python 2 中,这应该做你想做的:

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

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

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

我知道这些问题会询问两个列表,其中一个列表比另一个列表多一个项目,但我想我会把这个给可能会发现这个问题的其他人。

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

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

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

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

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

这是一个这样做的班轮:

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

这个是基于上面 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]

这是一个使用列表推导式的单行代码,没有其他库:

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

这是另一种方法,如果您允许通过副作用更改初始 list1:

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

再买一个 python one-liner 可能有点晚了。 当两个列表的大小相等或不相等时,这会起作用。 一文不值的是它会修改 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']

我的看法:

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)]
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
from itertools import chain
list(chain(*zip('abc', 'def')))  # Note: this only works for lists of equal length
['a', 'd', 'b', 'e', 'c', 'f']

在最短的时间停止:

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__ 方法可能会更快。

另一个问题答案启发的多个单行:

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]

麻麻呢? 它也适用于字符串:

import numpy as np

np.array([[a,b] for a,b in zip([1,2,3],[2,3,4,5,6])]).ravel()

结果:

array([1, 2, 2, 3, 3, 4])

功能和不可变方式的替代方案(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),[])

itertools.zip_longest返回一个元组对的迭代器,其中一个列表中的任何缺失元素都替换为fillvalue=None (传递fillvalue=object允许您使用None作为值)。 如果您展平这些对,然后在列表理解中过滤fillvalue ,则给出:

>>> from itertools import zip_longest
>>> def merge(a, b):
...     return [
...         x for y in zip_longest(a, b, fillvalue=object)
...         for x in y if x is not object
...     ]
...
>>> merge("abc", "defgh")
['a', 'd', 'b', 'e', 'c', 'f', 'g', 'h']
>>> merge([0, 1, 2], [4])
[0, 4, 1, 2]
>>> merge([0, 1, 2], [4, 5, 6, 7, 8])
[0, 4, 1, 5, 2, 6, 7, 8]

推广到任意迭代:

>>> def merge(*its):
...     return [
...         x for y in zip_longest(*its, fillvalue=object)
...         for x in y if x is not object
...     ]
...
>>> merge("abc", "lmn1234", "xyz9", [None])
['a', 'l', 'x', None, 'b', 'm', 'y', 'c', 'n', 'z', '1', '9', '2', '3', '4']
>>> merge(*["abc", "x"]) # unpack an iterable
['a', 'x', 'b', 'c']

最后,您可能想要返回一个生成器而不是列表推导式:

>>> def merge(*its):
...     return (
...         x for y in zip_longest(*its, fillvalue=object)
...         for x in y if x is not object
...     )
...
>>> merge([1], [], [2, 3, 4])
<generator object merge.<locals>.<genexpr> at 0x000001996B466740>
>>> next(merge([1], [], [2, 3, 4]))
1
>>> list(merge([1], [], [2, 3, 4]))
[1, 2, 3, 4]

如果你对其他包没问题,你可以试试more_itertools.roundrobin

>>> list(roundrobin('ABC', 'D', 'EF'))
['A', 'D', 'E', 'B', 'F', 'C']

使用 for 循环我们也可以轻松实现:

list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']
list3 = []

for i in range(len(list1)):
    #print(list3)
    list3.append(list1[i])
    if i < len(list2):
        list3.append(list2[i])
        
print(list3)

输出 :

['f', 'hello', 'o', 'world', 'o']

进一步通过使用列表推导可以减少这种情况。 但是为了理解这个循环可以使用。

显然迟到了,但这里有一个简明的等长列表:

output = [e for sub in zip(list1,list2) for e in sub]

它也泛化了任意数量的等长列表:

output = [e for sub in zip(list1,list2,list3) for e in sub]

等等

我的方法如下所示:

from itertools import chain, zip_longest

def intersperse(*iterators):
    # A random object not occurring in the iterators
    filler = object()

    r = (x for x in chain.from_iterable(zip_longest(*iterators, fillvalue=filler)) if x is not filler)

    return r

list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']

print(list(intersperse(list1, list2)))

它适用于任意数量的迭代器并产生一个迭代器,因此我在打印行中应用了list()

def alternate_elements(small_list, big_list):
mew = []
count = 0
for i in range(len(small_list)):
    mew.append(small_list[i])
    mew.append(big_list[i])
    count +=1
return mew+big_list[count:]

if len(l2)>len(l1):
    res = alternate_elements(l1,l2)
else:
    res = alternate_elements(l2,l1)

print(res)

在这里我们根据大小交换列表并执行,有人可以提供时间复杂度为 O(len(l1)+len(l2)) 的更好解决方案吗

这很讨厌,但无论列表的大小如何都有效:

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

我会做简单的:

chain.from_iterable( izip( list1, list2 ) )

它将提供一个迭代器,而不会产生任何额外的存储需求。

我太老了,不能理解列表,所以:

import operator
list3 = reduce(operator.add, zip(list1, list2))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM