簡體   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