İki listeyi alternatif bir şekilde birleştirmenin Pythonic yolu?


91

Birincisinin ikinciden tam olarak bir tane daha fazla öğe içermesi garantili iki listem var . Çift indeks değerleri ilk listeden ve tek indeks değerleri ikinci listeden gelen yeni bir liste oluşturmanın en Pythonic yolunu bilmek istiyorum.

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

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

Bu işe yarıyor, ancak hoş değil:

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

Bu başka nasıl başarılabilir? En Pythonic yaklaşım nedir?



Kopya değil! Yukarıdaki bağlantılı makaledeki kabul edilen cevap, tek bir birleştirilmiş liste değil, tuple listesi oluşturur.
Paul Sasik

@Paul: Evet, kabul edilen cevap tam çözümü vermiyor. Yorumları ve diğer cevapları okuyun. Soru temelde aynıdır ve diğer çözümler burada uygulanabilir.
Felix Kling

3
@Felix: Saygıyla katılmıyorum. Doğru, sorular aynı mahallede ama gerçekte aynı değil. Belirsiz bir kanıt olarak, buradaki potansiyel cevaplara bir göz atın ve diğer soruyla karşılaştırın.
Paul Sasik

Yanıtlar:


119

Bunu dilimleyerek yapmanın bir yolu:

>>> 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
Teşekkürler Duncan. Dilimleme sırasında bir adım belirlemenin mümkün olduğunu fark etmemiştim. Bu yaklaşımın sevdiğim yanı ne kadar doğal okuduğu. 1. Doğru uzunlukta bir liste yapın. 2. Çift dizinleri list1 içeriğiyle doldurdu. 3. Tekli dizinleri list2'nin içeriğiyle doldurun. Listelerin farklı uzunluklarda olması bu durumda sorun değil!
davidchambers

2
Sadece len (list1) - len (list2) 0 veya 1 olduğunda işe yaradığını düşünüyorum.
xan

1
Listeler uygun uzunluktaysa işe yarar, değilse, o zaman orijinal soru hangi yanıtın beklendiğini belirtmez. En makul durumların üstesinden gelmek için kolayca değiştirilebilir: örneğin, fazladan öğelerin göz ardı edilmesini istiyorsanız, başlamadan önce uzun listeyi kesin; Ekstra elemanların Hiçbiri ile serpiştirilmesini istiyorsanız, sonucun daha fazla Yok ile başlatıldığından emin olun; Ekstra elemanların sonuna eklenmesini istiyorsanız, onları görmezden gelin ve sonra ekleyin.
Duncan

1
Ben de belirsizdim. Bahsetmeye çalıştığım nokta, Duncan'ın çözümünün, listelenenlerin çoğunun aksine, listelerin eşit uzunlukta olmaması nedeniyle karmaşık olmadığıdır. Elbette, yalnızca sınırlı bir durumda uygulanabilir, ancak bu örnekte çalışan gerçekten zarif bir çözümü, herhangi iki liste için çalışan daha az zarif bir çözüme tercih ederim.
davidchambers

1
(Len (list1) + len (list2)) yerine (2 * len (list1) -1) kullanabilirsiniz, ayrıca [:: 2] yerine [0 :: 2] 'yi tercih ederim.
Lord British

51

itertoolsDokümantasyonda bunun için bir tarif var :

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

DÜZENLE:

Python'un 3'ten büyük versiyonu için:

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

Bu yolu olması gerekenden daha karmaşık buluyorum. Aşağıda daha iyi bir seçenek var zip_longest.
Dubslow

@Dubslow Bu özel durum için, evet, zaten erişiminiz yoksa, bu muhtemelen aşırıdır (başka bir yerde bir yorumda bahsettiğim gibi). Yine de diğer durumlarda bazı avantajları olabilir. Bu tarif kesinlikle bu problem için tasarlanmadı, sadece onu çözmek için oluyor.
David Z

1
Bilginize içeri tarifi kullanmalıdır itertools belgelere çünkü .next()artık çalışır.
John w.

1
@johnw. kullanmak zorunda __next__. Dokümantasyonda yazılı değil, bu yüzden cevaba bir düzenleme önerdim.
Deniz Galantin

@Marine Mevcut kod örneğini değiştirmiş olmanı tercih ederim, ama bunu kendim düzeltebilirim. Katkıda bulunduğunuz için teşekkürler!
David Z

31

Bu istediğini yapmalı:

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

İlk cevabını gerçekten beğendim. Soruyu tam olarak cevaplamasa da, aynı uzunlukta iki listeyi birleştirmenin zarif bir yoluydu. Mevcut cevabınızda uzunluk uyarısı ile birlikte saklamanızı öneririm.
Paul Sasik

1
Eğer liste1 yerine ['f', 'o', 'o', 'd'] olsaydı, son öğesi ('d') sonuç listesinde görünmezdi (sorunun özellikleri göz önüne alındığında bu tamamen iyidir). Bu zarif bir çözüm!
davidchambers

1
@Mark yep (oy verdim), sadece farklılıklara (ve diğer insanlar farklı davranışlar istiyorsa sınırlamalara) dikkat
çekiyorum

4
Belirtilen problemi çözmek için +1 ve bunu basitçe yapmak için :-) Böyle bir şeyin mümkün olacağını düşündüm. Dürüst olmak gerekirse, roundrobinişlevin bu durum için biraz fazla olduğunu düşünüyorum .
David Z

1
Herhangi bir boyuttaki listeyle çalışmak için yineleyicilerde kalanları sonuca ekleyebilirsiniz: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]

Bence bunu yapmanın en pitonik yolu bu.


3
Neden kabul edilen cevap bu değil? Bu en kısa ve en pitoniktir ve farklı liste uzunluklarıyla çalışır!
Jairo Vadillo

5
yöntem adı zip_longest değil izip_longest
Jairo Vadillo

1
Bununla ilgili sorun, zip_longest'in varsayılan doldurma değerinin Nonelistede olması gereken * 'lerin üzerine yazabilmesidir . Bunu düzeltmek için
ince ayarlanmış

Not: listeler değerle unsurları içeriyorsa, bu sorunlara neden olur False, yoksa yalnızca edilecektir hatta şeyleri değerlendirdi olarak Falsetarafından iförnek a yönelik gibi -Anlatım 0veya boş bir listeyle. Bu olabilir (kısmen), aşağıdaki şekilde önlenir: [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. Elbette, listelerde Nonekorunması gereken öğeler varsa, bu yine de çalışmayacaktır . Bu durumda, Dubslow'un zaten önerdiği gibi fillvalueargümanını değiştirmeniz gerekir zip_longest.
der_herr_g

Nonesorun, en azından Python 3.7.6'dan beri ortadan kalkmış görünüyor (eski sürümleri bilmiyorum). Eğer alt_chainolarak tanımlanır def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue)), daha sonra list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))doğru bir şekilde geri döner [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].
paime

18

Yineleme araçları olmadan ve l1'in l2'den 1 öğe daha uzun olduğunu varsayarsak:

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

Yineleme araçlarını kullanma ve listelerin Hiçbiri içermediğini varsayarak:

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

Bu benim favori cevabım. Çok özlü.
mbomb007

@ anishtain4 zip, listelerden tuple olarak öğe çiftlerini alır [(l1[0], l2[0]), (l1[1], l2[1]), ...],. sumdemetleri birbirine birleştirir: (l1[0], l2[0]) + (l1[1], l2[1]) + ...aralıklı listelerle sonuçlanır. Tek satırın geri kalanı, zip'in çalışması ve bu dolgudan kurtulmak için -1'e kadar dilimlenmesi için fazladan eleman içeren l1 dolgusu.
Zart

izip_longest (zip_longest çünkü python 3) + [0] dolgusuna ihtiyaç duymaz, listelerin uzunlukları eşleşmediğinde örtük olarak Yok doldurur, filter(None, ...( boolbunun yerine kullanılabilir veya None.__ne__) 0, Yok ve boş dizeler dahil olmak üzere yanlış değerleri kaldırır, bu nedenle ikinci ifade, birincisine tam olarak eşdeğer değildir.
Zart

Soru, bunu nasıl yaptın sum? Oradaki ikinci argümanın rolü nedir? Belgelerde ikinci argüman şudur start.
anishtain4

Başlangıç ​​için varsayılan değer 0'dır ve 0+ yapamazsınız (bazı, tuple), bu nedenle başlangıç ​​boş tuple olarak değiştirilir.
Zart

13

Soruların, biri diğerinden daha fazla öğeye sahip iki listeyle ilgili sorduğunu biliyorum, ama bunu bu soruyu bulabilecek diğerlerine koyacağımı düşündüm.

İşte Duncan'ın çözümü , farklı boyutlarda iki listeyle çalışmak üzere uyarlanmıştır.

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

Bu çıktılar:

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

8

Her iki listenin uzunluğu da eşitse şunları yapabilirsiniz:

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

İlk listenin bir öğesi daha olduğundan, post hoc ekleyebilirsiniz:

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

3
^ Cevap bu olmalı, python son 10 yılda daha pitonik hale geldi
Tian

5

İşte bunu yapan tek bir satır:

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


2
Bu doğru bir şekilde çalışıyor, ancak bu kadar basit bir şeyi başarmak için çok şey yaptığı için bana uygunsuz geliyor. Bu yaklaşımın verimsiz olduğunu söylemiyorum, basitçe okunması özellikle kolay değil.
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

Değişken varsayılan argümanlar kullanırken çok dikkatli olun. Bu, daha sonra her aramada tekrar kullanılacağından, bu yalnızca ilk çağrıldığında doğru yanıtı döndürecektir. Bu, lst = Yok ... şeklinde yazılsa daha iyi olur ... eğer lst Yok ise: lst = [], ancak burada listelenen diğerlerine göre bu yaklaşımı tercih etmek için ikna edici bir neden görmüyorum.
davidchambers

lst fonksiyon içinde tanımlanır, bu yüzden yerel bir değişken. Olası sorun, işlevi farklı listelerle çağırsanız bile, list1 ve list2'nin işlevi her kullanışınızda yeniden kullanılmasıdır. Bkz. Docs.python.org/tutorial/…
blokeley

1
@blokeley: yanlış, birleştirilirse yeniden kullanılır (list1 = [...], list2 = [...])
killown

Bu çözüm ilk yayınlandığında ilk satırı okundu def combine(list1, list2, lst=[]):, dolayısıyla benim yorumum. Yine de bu yorumu yaptığımda, fırın gerekli değişikliği yapmıştı.
davidchambers

2

Bu, Carlos Valiente'nin, birden çok öğeden oluşan grupları değiştirme ve tüm öğelerin çıktıda mevcut olduğundan emin olma seçeneği ile yukarıdaki katkısına dayanmaktadır:

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

Bu, A ve B listelerini her biri 3 değerden oluşan gruplarla birleştirir:

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

Yine bir python tek astar satın almak biraz geç olabilir. Bu, iki liste eşit veya eşit olmayan boyuta sahip olduğunda çalışır. Değersiz olan tek şey, a ve b'yi değiştirecek olmasıdır. Bu bir sorunsa, başka çözümler kullanmanız gerekir.

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

Benim almam:

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

İşte diğer kitaplıklar olmadan liste anlamalarını kullanan bir satır:

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

İlk listenizin1 yan etkiyle değiştirilmesine izin verirseniz, işte başka bir yaklaşım:

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

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

En kısa zamanda durur:

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

Elbette, sonraki / __ sonraki__ yöntemini çözmek daha hızlı olabilir.


0

Bu iğrenç ama listelerin boyutu ne olursa olsun işe yarıyor:

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

0

Başka bir sorunun yanıtlarından ilham alan birden fazla tek satırlık yazı :

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

Peki ya uyuşuk? Dizelerle de çalışır:

import numpy as np

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

Sonuç:

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

0

İşlevsel ve değişmez bir şekilde bir alternatif (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

Ben basit yapardım:

chain.from_iterable( izip( list1, list2 ) )

Herhangi bir ek depolama ihtiyacı yaratmadan bir yineleyici bulacaktır.


1
Gerçekten basit, ancak yalnızca aynı uzunluktaki listelerde işe yarıyor!
Jochen Ritzel

chain.from_iterable(izip(list1, list2), list1[len(list2):])Burada sorulan belirli bir sorun için düzeltebilirsiniz ... list1'in daha uzun olması gerekiyor.
Jochen Ritzel

Evet, ancak keyfi uzunluktaki kaplar için işe yarayan bir çözüm bulmayı veya yukarıda önerilen çözümlerden yararlanmayı tercih ederim.
buğdaylar

-2

Liste anlayışları için çok yaşlıyım, bu yüzden:

import operator
list3 = reduce(operator.add, zip(list1, list2))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.