Cara pythonic untuk menggabungkan dua daftar secara bergantian?

91

Saya punya dua daftar, yang pertama dijamin berisi tepat satu item lebih banyak daripada yang kedua . Saya ingin tahu cara paling Pythonic untuk membuat daftar baru yang nilai indeks genapnya berasal dari daftar pertama dan yang nilai indeks ganjilnya berasal dari daftar kedua.

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

# desired output
['f', 'hello', 'o', 'world', 'o']

Ini berfungsi, tetapi tidak cantik:

list3 = []
while True:
    try:
        list3.append(list1.pop(0))
        list3.append(list2.pop(0))
    except IndexError:
        break

Bagaimana lagi ini bisa dicapai? Apa pendekatan yang paling Pythonic?

davidchambers.dll
sumber
2
kemungkinan duplikat Bergantian antara iterator dengan Python
Felix Kling
Bukan duplikat! Jawaban yang diterima dalam artikel yang ditautkan di atas menghasilkan daftar tupel, bukan daftar tunggal yang digabungkan.
Paul Sasik
@Paul: Ya, jawaban yang diterima tidak memberikan solusi lengkap. Baca komentar dan jawaban lainnya. Pertanyaannya pada dasarnya sama dan solusi lain dapat diterapkan di sini.
Felix Kling
3
@ Felix: Saya dengan hormat tidak setuju. Memang benar, pertanyaannya ada di lingkungan yang sama tetapi tidak benar-benar duplikat. Sebagai bukti samar, lihat jawaban potensial di sini dan bandingkan dengan pertanyaan lain.
Paul Sasik

Jawaban:

119

Inilah salah satu cara melakukannya dengan mengiris:

>>> 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']
Duncan
sumber
3
Terima kasih, Duncan. Saya tidak menyadari bahwa mungkin menentukan langkah saat mengiris. Yang saya suka dari pendekatan ini adalah bagaimana cara membaca secara alami. 1. Buat daftar panjang yang benar. 2. Diisi indeks genap dengan isi list1. 3. Isi indeks ganjil dengan isi list2. Fakta bahwa panjang daftar tersebut berbeda-beda bukanlah masalah dalam kasus ini!
davidchambers
2
Saya pikir itu hanya berfungsi ketika len (list1) - len (list2) adalah 0 atau 1.
xan
1
Jika daftar memiliki panjang yang sesuai maka itu berhasil, jika tidak maka pertanyaan asli tidak menentukan jawaban apa yang diharapkan. Ini dapat dengan mudah dimodifikasi untuk menangani situasi yang paling masuk akal: misalnya jika Anda ingin elemen tambahan diabaikan, potong saja daftar yang lebih panjang sebelum Anda mulai; jika Anda ingin elemen tambahan disisipkan dengan None maka pastikan bahwa hasilnya diinisialisasi dengan beberapa None; jika Anda ingin elemen tambahan baru saja ditambahkan di akhir, lakukan seperti untuk mengabaikannya dan kemudian menambahkannya.
Duncan
1
Saya juga tidak jelas. Hal yang ingin saya sampaikan adalah bahwa solusi Duncan, tidak seperti banyak dari yang terdaftar, tidak dipersulit oleh fakta bahwa panjang daftar tidak sama. Tentu, ini hanya berlaku dalam berbagai situasi terbatas, tetapi saya lebih suka solusi yang sangat elegan yang berfungsi dalam hal ini daripada solusi yang kurang elegan yang berfungsi untuk dua daftar mana pun.
davidchambers
1
Anda dapat menggunakan (2 * len (list1) -1) daripada (len (list1) + len (list2)), saya juga lebih suka [0 :: 2] daripada [:: 2].
Lord British
51

Ada resep untuk ini di itertoolsdokumentasi :

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

EDIT:

Untuk versi python lebih dari 3:

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))
David Z
sumber
Menurut saya cara ini lebih rumit dari yang seharusnya. Ada opsi yang lebih baik di bawah ini menggunakan zip_longest.
Dubslow
@Dubslow Untuk kasus khusus ini, ya, ini mungkin berlebihan (seperti yang saya sebutkan dalam komentar di tempat lain), kecuali Anda kebetulan sudah memiliki akses ke sana. Ini mungkin memiliki beberapa keuntungan dalam situasi lain. Resep ini jelas tidak dirancang untuk masalah ini, hanya kebetulan saja untuk mengatasinya.
David Z
1
fyi Anda harus menggunakan resep dalam itertools dokumentasi karena .next()tidak berfungsi lagi.
john w.
1
@tokopedia seseorang harus menggunakan __next__. Itu tidak tertulis dalam dokumentasi jadi saya mengusulkan edit untuk jawabannya.
Marine Galantin
@ Marin Saya lebih suka Anda baru saja mengubah sampel kode yang ada, tetapi saya dapat memperbaikinya sendiri. Terima kasih telah berkontribusi!
David Z
31

Ini harus melakukan apa yang Anda inginkan:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']
Mark Byers
sumber
Saya sangat menyukai jawaban awal Anda. Meskipun tidak secara sempurna menjawab pertanyaan tersebut, ini adalah cara yang elegan untuk menggabungkan dua daftar dengan panjang yang sama. Saya sarankan untuk menyimpannya, bersama dengan peringatan panjang, dalam jawaban Anda saat ini.
Paul Sasik
1
Jika list1 bukan ['f', 'o', 'o', 'd'], item akhirnya ('d') tidak akan muncul dalam daftar yang dihasilkan (yang benar-benar bagus mengingat spesifikasi pertanyaannya). Ini adalah solusi yang elegan!
davidchambers
1
@Mark yep (Saya melakukan upvote itu), hanya menunjukkan perbedaan (dan batasan jika orang lain menginginkan perilaku yang berbeda)
cobbal
4
1 untuk memecahkan masalah yang dinyatakan, dan untuk melakukannya juga :-) Saya pikir sesuatu seperti ini akan mungkin dilakukan. Sejujurnya saya pikir roundrobinfungsinya agak berlebihan untuk situasi ini.
David Z
1
Untuk bekerja dengan daftar ukuran berapa pun, Anda cukup menambahkan apa yang tersisa di iterator ke hasil:list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
panda-34
30
import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

Saya pikir ini adalah cara paling pythonic untuk melakukannya.

pengguna942640
sumber
3
Mengapa ini bukan jawaban yang diterima? Ini adalah yang terpendek dan paling pythonic dan bekerja dengan panjang daftar yang berbeda!
Jairo Vadillo
5
nama metode zip_longest bukan izip_longest
Jairo Vadillo
1
Masalahnya adalah bahwa nilai fillin default dari zip_longest mungkin menimpa Nones yang * seharusnya ada dalam daftar. Saya akan mengedit dalam versi tweak untuk memperbaikinya
Dubslow
Catatan: Ini akan menimbulkan masalah jika daftar berisi elemen dengan nilai False , atau bahkan hal-hal yang hanya akan dievaluasi sebagai Falseoleh if-expression, seperti misalnya 0atau daftar kosong. Hal ini dapat (sebagian) dihindari dengan berikut: [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. Tentu saja, ini tetap tidak akan berhasil jika daftar berisi Noneelemen yang perlu dipertahankan. Dalam hal ini, Anda perlu mengubah fillvalueargumen zip_longest, seperti yang sudah disarankan Dubslow.
der_herr_g
NoneMasalah tampaknya hilang, setidaknya sejak Python 3.7.6 (saya tidak tahu untuk versi yang lebih lama). Jika alt_chaindidefinisikan sebagai def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue)), maka list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))mengembalikan dengan benar [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].
paime
18

Tanpa itertools dan mengasumsikan l1 adalah 1 item lebih panjang dari l2:

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

Menggunakan itertools dan berasumsi bahwa daftar tidak berisi None:

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')
Zart
sumber
Ini adalah jawaban favorit saya. Ini sangat ringkas.
mbomb007
@ anishtain4 zip mengambil pasangan elemen sebagai tupel dari daftar [(l1[0], l2[0]), (l1[1], l2[1]), ...],. summenggabungkan tupel bersama: (l1[0], l2[0]) + (l1[1], l2[1]) + ...menghasilkan daftar berselang-seling. Sisa satu baris hanyalah padding dari l1 dengan elemen tambahan agar zip berfungsi dan potong hingga -1 untuk menghilangkan padding itu.
Zart
izip_longest (zip_longest sejak python 3) tidak membutuhkan + [0] padding, itu secara implisit mengisi None ketika panjang daftar tidak cocok, sementara filter(None, ...(bisa digunakan boolsebagai gantinya, atau None.__ne__) menghapus nilai yang salah, termasuk 0, None dan string kosong, jadi ekspresi kedua tidak sepenuhnya sama dengan yang pertama.
Zart
Pertanyaannya adalah bagaimana Anda summelakukannya? Apa peran argumen kedua di sana? Dalam dokumentasinya, argumen kedua adalah start.
anishtain4
Nilai default start adalah 0, dan Anda tidak dapat melakukan 0+ (beberapa, tuple), maka start diubah menjadi tupel kosong.
Zart
13

Saya tahu pertanyaan menanyakan tentang dua daftar dengan satu memiliki satu item lebih banyak dari yang lain, tetapi saya pikir saya akan menanyakan ini untuk orang lain yang mungkin menemukan pertanyaan ini.

Berikut adalah solusi Duncan yang disesuaikan untuk bekerja dengan dua daftar ukuran berbeda.

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

Output ini:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 
mhost
sumber
8

Jika kedua daftar memiliki panjang yang sama, Anda dapat melakukan:

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

Karena daftar pertama memiliki satu elemen lagi, Anda dapat menambahkannya post hoc:

[x for y in zip(list1, list2) for x in y] + [list1[-1]]
Some one
sumber
3
^ Ini seharusnya jawabannya, python telah menjadi lebih pythonic dalam 10 tahun terakhir
Tian
5

Inilah satu liner yang melakukannya:

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

Jay
sumber
2
Ini bekerja dengan benar tetapi menurut saya tidak elegan karena melakukan begitu banyak hal untuk mencapai sesuatu yang sangat sederhana. Saya tidak mengatakan bahwa pendekatan ini tidak efisien, hanya karena tidak mudah dibaca.
davidchambers
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
killown
sumber
Berhati-hatilah saat menggunakan argumen default yang bisa berubah. Ini hanya akan mengembalikan jawaban yang benar saat pertama kali dipanggil, karena lst akan digunakan kembali untuk setiap panggilan setelahnya. Ini akan lebih baik ditulis sebagai lst = None ... jika lst is None: lst = [], meskipun saya tidak melihat alasan yang kuat untuk memilih pendekatan ini dibandingkan pendekatan lain yang tercantum di sini.
davidchambers
lst didefinisikan di dalam fungsi begitu juga dengan variabel lokal. Masalah potensial adalah bahwa list1 dan list2 akan digunakan kembali setiap kali Anda menggunakan fungsi tersebut, bahkan jika Anda memanggil fungsi dengan daftar yang berbeda. Lihat docs.python.org/tutorial/…
blokeley
1
@blokeley: salah, akan digunakan kembali jika digabungkan (list1 = [...], list2 = [...])
killown
Ketika solusi ini pertama kali diposting, baris pertamanya dibaca def combine(list1, list2, lst=[]):, itulah komentar saya. Namun, pada saat saya mengirimkan komentar itu, killown telah membuat perubahan yang diperlukan.
davidchambers
2

Yang ini didasarkan pada kontribusi Carlos Valiente di atas dengan opsi untuk mengganti kelompok yang terdiri dari banyak item dan memastikan bahwa semua item ada dalam keluaran:

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

Ini akan menghubungkan daftar A dan B dengan kelompok dari 3 nilai masing-masing:

['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]
catpnced
sumber
2

Mungkin agak terlambat membeli satu lagi python one-liner. Ini berfungsi jika kedua daftar memiliki ukuran yang sama atau tidak sama. Satu hal yang tidak berharga adalah ia akan memodifikasi a dan b. Jika ini merupakan masalah, Anda perlu menggunakan solusi lain.

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']
Allen
sumber
1

Saya mengambil:

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)]
Carlos Valiente
sumber
1

Berikut satu liner yang menggunakan pemahaman daftar, tanpa pustaka lain:

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

Berikut adalah pendekatan lain, jika Anda mengizinkan perubahan list1 awal Anda dengan efek samping:

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]
chernevik.dll
sumber
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']
Ken Seehart
sumber
0

Berhenti paling cepat:

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

Tentu, menyelesaikan metode berikutnya / __ next__ mungkin lebih cepat.

jwp
sumber
0

Ini buruk tetapi berfungsi tidak peduli ukuran daftarnya:

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

Beberapa kalimat satu baris terinspirasi oleh jawaban untuk pertanyaan lain :

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]
kata-kata
sumber
0

Bagaimana dengan numpy? Ia bekerja dengan string juga:

import numpy as np

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

Hasil:

array([1, 2, 2, 3, 3, 4])
Nikolay Frick
sumber
0

Alternatif dengan cara yang fungsional & tidak berubah (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),[])
Godot
sumber
-1

Saya akan melakukan yang sederhana:

chain.from_iterable( izip( list1, list2 ) )

Ini akan muncul dengan iterator tanpa membuat kebutuhan penyimpanan tambahan.

wheaties
sumber
1
Ini sangat sederhana, tetapi hanya berfungsi dengan daftar dengan panjang yang sama!
Jochen Ritzel
Anda dapat memperbaikinya dengan chain.from_iterable(izip(list1, list2), list1[len(list2):])untuk masalah khusus yang ditanyakan di sini ... list1 seharusnya menjadi yang lebih panjang.
Jochen Ritzel
Ya, tetapi saya lebih suka menemukan solusi yang berfungsi untuk wadah dengan panjang arbitrer atau menghasilkan solusi yang diusulkan di atas.
Wheaties
-2

Saya terlalu tua untuk memahami daftar, jadi:

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