Pythonowy sposób na połączenie dwóch list na przemian?

88

Mam dwie listy, z których pierwsza na pewno zawiera dokładnie jedną pozycję więcej niż druga . Chciałbym poznać najbardziej Pythonowy sposób tworzenia nowej listy, której parzyste wartości indeksu pochodzą z pierwszej listy, a nieparzyste wartości z drugiej listy.

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

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

To działa, ale nie jest ładne:

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

Jak inaczej można to osiągnąć? Jakie jest najbardziej Pythonic podejście?

davidchambers
źródło
1
możliwy duplikat Naprzemiennie między iteratorami w Pythonie
Felix Kling
To nie jest duplikat! Zaakceptowana odpowiedź w powyższym artykule tworzy listę krotek, a nie pojedynczą, scaloną listę.
Paul Sasik
@Paul: Tak, zaakceptowana odpowiedź nie daje pełnego rozwiązania. Przeczytaj komentarze i inne odpowiedzi. Pytanie jest w zasadzie to samo i można tu zastosować inne rozwiązania.
Felix Kling
3
@Felix: Z całym szacunkiem się nie zgadzam. To prawda, pytania znajdują się w tej samej okolicy, ale tak naprawdę nie są duplikatami. Jako niejasny dowód spójrz na potencjalne odpowiedzi tutaj i porównaj z innym pytaniem.
Paul Sasik
Sprawdź te: stackoverflow.com/questions/7529376/ ...
wordsforthewise

Odpowiedzi:

112

Oto jeden sposób na zrobienie tego przez cięcie:

>>> 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
źródło
3
Dzięki, Duncan. Nie zdawałem sobie sprawy, że można określić krok podczas krojenia. W tym podejściu podoba mi się to, jak naturalnie się czyta. 1. Sporządź listę o odpowiedniej długości. 2. Zapełniono indeksy parzyste zawartością listy1. 3. Wypełnij nieparzyste indeksy zawartością listy2. Fakt, że listy mają różne długości, nie stanowi w tym przypadku problemu!
davidchambers
2
Myślę, że działa tylko wtedy, gdy len (list1) - len (list2) wynosi 0 lub 1.
xan
1
Jeśli listy mają odpowiednią długość, to działa, jeśli nie, to pierwotne pytanie nie określa, jakiej odpowiedzi się oczekuje. Można go łatwo zmodyfikować, aby poradzić sobie z większością rozsądnych sytuacji: na przykład, jeśli chcesz, aby dodatkowe elementy zostały zignorowane, po prostu skróć dłuższą listę przed rozpoczęciem; jeśli chcesz, aby dodatkowe elementy były przeplatane wartością None, po prostu upewnij się, że wynik jest zainicjowany kilkoma innymi None; jeśli chcesz, aby dodatkowe elementy zostały dodane na końcu, zignoruj ​​je, a następnie dołącz.
Duncan
1
Ja też byłam niejasna. Chodzi mi o to, że rozwiązanie Duncana, w przeciwieństwie do wielu z wymienionych, nie jest skomplikowane przez fakt, że listy mają nierówną długość. Jasne, ma to zastosowanie tylko w ograniczonym zakresie sytuacji, ale wolałbym naprawdę eleganckie rozwiązanie, które działa w tym przypadku, od mniej eleganckiego rozwiązania, które działa dla dowolnych dwóch list.
davidchambers
1
Możesz użyć (2 * len (list1) -1) zamiast (len (list1) + len (list2)), ja też wolę [0 :: 2] zamiast [:: 2].
Lord British,
50

Jest to przepis na to w itertoolsdokumentacji :

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

EDYTOWAĆ:

Dla wersji Pythona większej niż 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
źródło
Uważam, że jest to bardziej skomplikowane, niż powinno. Poniżej znajduje się lepsza opcja zip_longest.
Dubslow
@Dubslow W tym konkretnym przypadku jest to prawdopodobnie przesada (jak wspomniałem w innym komentarzu), chyba że masz już do niego dostęp. Może to mieć jednak pewne zalety w innych sytuacjach. Ten przepis z pewnością nie został stworzony z myślą o tym problemie, po prostu go rozwiązuje.
David Z
1
fyi powinieneś użyć przepisu w itertools dokumentacji, ponieważ .next()już nie działa.
Jan W.
1
@johnw. trzeba użyć __next__. Nie ma tego w dokumentacji, więc zaproponowałem edycję odpowiedzi.
Marine Galantin
@Marine Wolałbym raczej, abyś zmienił istniejący przykładowy kod, ale mogę to naprawić samodzielnie. Dzięki za wkład!
David Z
31

Powinno to zrobić, co chcesz:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']
Mark Byers
źródło
Bardzo podobała mi się twoja początkowa odpowiedź. Chociaż nie odpowiadał on idealnie na pytanie, był to elegancki sposób łączenia dwóch list o tej samej długości. Proponuję zachować go wraz z zastrzeżeniem dotyczącym długości w obecnej odpowiedzi.
Paul Sasik
1
Gdyby lista1 była zamiast [„f”, „o”, „o”, „d”], jej ostatnia pozycja („d”) nie pojawiłaby się na wynikowej liście (co jest całkowicie w porządku, biorąc pod uwagę specyfikę pytania). To eleganckie rozwiązanie!
davidchambers
1
@Mark tak (zagłosowałem za tym), po prostu wskazując różnice (i ograniczenia, jeśli inni chcą innego zachowania)
cobbal
4
+1 za rozwiązanie podanego problemu i za zrobienie tego po prostu :-) Pomyślałem, że coś takiego będzie możliwe. Szczerze mówiąc, myślę, że ta roundrobinfunkcja jest przesadą w tej sytuacji.
David Z
1
Aby pracować z listami o dowolnym rozmiarze, możesz po prostu dołączyć do wyniku to, co zostało w iteratorach: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]

Myślę, że to najbardziej pytoniczny sposób na zrobienie tego.

user942640
źródło
3
Dlaczego to nie jest akceptowana odpowiedź? To jest najkrótsza i najbardziej pythonowa i działa z różnymi długościami list!
Jairo Vadillo
5
nazwa metody to zip_longest, a nie izip_longest
Jairo Vadillo
1
Problem polega na tym, że domyślna wartość wypełnienia z zip_longest może nadpisać znaki None, które * powinny znajdować się na liście. Zmienię poprawioną wersję, aby to naprawić
Dubslow
Uwaga: będzie to powodować problemy jeżeli wykazy zawierają elementy o wartości False, a nawet rzeczy, które będą tylko oceniane jako Falseprzez if-expression, jak na przykład 0lub pustej listy. Może to być (częściowo) unika się za pomocą następujących: [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. Oczywiście to nadal nie zadziała, jeśli listy zawierają Noneelementy, które należy zachować. W takim przypadku musisz zmienić fillvalueargument zip_longest, jak już zasugerował Dubslow.
der_herr_g
NoneWydaje się, że problem zniknął, przynajmniej od czasu Pythona 3.7.6 (nie znam starszych wersji). Jeśli alt_chainjest zdefiniowane jako def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue)), to list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))poprawnie zwraca [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].
płatność
17

Bez itertools i zakładając, że l1 jest o 1 element dłuższy niż l2:

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

Korzystanie z itertools i zakładanie, że listy nie zawierają None:

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')
Zart
źródło
To moja ulubiona odpowiedź. To takie zwięzłe.
mbomb007
@ anishtain4 zip trwa par elementów jak krotek z list [(l1[0], l2[0]), (l1[1], l2[1]), ...]. sumłączy ze sobą krotki, (l1[0], l2[0]) + (l1[1], l2[1]) + ...co daje przeplatane listy. Reszta jednej linii to tylko wypełnienie l1 dodatkowym elementem, aby zip działał i kroił do -1, aby pozbyć się tego wypełnienia.
Zart
izip_longest (zip_longest od pythona 3) nie potrzebuje dopełnienia + [0], niejawnie wypełnia None, gdy długości list nie pasują, podczas gdy filter(None, ...(można użyć boolzamiast tego lub None.__ne__) usuwa fałszywe wartości, w tym 0, None i puste ciągi, więc drugie wyrażenie nie jest ściśle równoważne z pierwszym.
Zart
Pytanie brzmi, jak to sumzrobiłeś? Jaka jest tam rola drugiego argumentu? W dokumentacjach drugim argumentem jest start.
anishtain 4
Domyślna wartość startu to 0 i nie możesz zrobić 0+ (trochę, krotka), więc start jest zamieniany na pustą krotkę.
Zart
13

Wiem, że pytania dotyczą dwóch list, z których jedna ma o jeden element więcej niż druga, ale pomyślałem, że zadam to innym, którzy mogą znaleźć to pytanie.

Oto rozwiązanie firmy Duncan przystosowane do pracy z dwiema listami o różnych rozmiarach.

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

To daje:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 
mhost
źródło
6

Jeśli obie listy mają taką samą długość, możesz:

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

Ponieważ pierwsza lista ma jeszcze jeden element, możesz dodać ją post hoc:

[x for y in zip(list1, list2) for x in y] + [list1[-1]]
Ktoś
źródło
2
^ To powinna być odpowiedź, python stał się bardziej pytoniczny w ciągu ostatnich 10 lat
Tian
5

Oto jedna linijka, która to robi:

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

Sójka
źródło
2
Działa to poprawnie, ale wydaje mi się nieeleganckie, ponieważ robi tak wiele, aby osiągnąć coś tak prostego. Nie mówię, że to podejście jest nieefektywne, po prostu nie jest szczególnie łatwe do odczytania.
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
źródło
Zachowaj ostrożność podczas używania zmiennych domyślnych argumentów. To zwróci poprawną odpowiedź tylko przy pierwszym wywołaniu, ponieważ lst będzie ponownie używany dla każdego następnego połączenia. Byłoby to lepiej napisane jako lst = None ... jeśli lst to None: lst = [], chociaż nie widzę przekonującego powodu, aby wybrać to podejście zamiast innych wymienionych tutaj.
davidchambers
lst jest zdefiniowane w funkcji, a więc jest zmienną lokalną. Potencjalnym problemem jest to, że lista1 i lista2 są ponownie użyte za każdym razem, gdy użyjesz funkcji, nawet jeśli wywołasz funkcję z różnymi listami. Zobacz docs.python.org/tutorial/…
blokeley
1
@blokeley: źle, zostanie ponownie użyte, jeśli zostanie połączone (list1 = [...], list2 = [...])
killown
Kiedy to rozwiązanie zostało po raz pierwszy opublikowane, jego pierwsza linia została odczytana def combine(list1, list2, lst=[]):, stąd mój komentarz. Zanim jednak przedstawiłem ten komentarz, killown dokonało koniecznej zmiany.
davidchambers,
2

Ten jest oparty na powyższym wkładzie Carlosa Valiente z opcją zmiany grup wielu elementów i upewnienia się, że wszystkie elementy są obecne w wyniku:

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

Spowoduje to przeplatanie list A i B grupami po 3 wartości:

['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
źródło
2

Może być trochę spóźniony, kup jeszcze jedną linijkę Pythona. Działa to, gdy obie listy mają równy lub nierówny rozmiar. Jedna rzecz nic nie jest warta to, że zmodyfikuje a i b. Jeśli jest to problem, musisz użyć innych rozwiązań.

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
źródło
1

Moje podanie:

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
źródło
1

Oto jedna linijka używająca wyrażeń listowych, bez innych bibliotek:

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

Oto inne podejście, jeśli pozwolisz na zmianę swojej początkowej listy1 przez efekt uboczny:

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]
chernevik
źródł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']
Ken Seehart
źródło
0

Zatrzymuje się na najkrótszym:

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

Jasne, rozwiązanie metody next / __ next__ może być szybsze.

jwp
źródło
0

To jest nieprzyjemne, ale działa niezależnie od rozmiaru list:

list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]
pająk rycerz
źródło
0

Wiele linijek inspirowanych odpowiedziami na inne pytanie :

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]
słowa do rozumu
źródło
0

Co powiesz na numpy? Działa również ze sznurkami:

import numpy as np

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

Wynik:

array([1, 2, 2, 3, 3, 4])
Nikolay Frick
źródło
0

Alternatywa w funkcjonalny i niezmienny sposób (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
źródło
-1

Zrobiłbym proste:

chain.from_iterable( izip( list1, list2 ) )

Pojawi się iterator bez tworzenia dodatkowych wymagań dotyczących pamięci.

pszenicy
źródło
1
To naprawdę proste, ale działa tylko z listami o tej samej długości!
Jochen Ritzel
Możesz to naprawić chain.from_iterable(izip(list1, list2), list1[len(list2):])za pomocą konkretnego problemu tutaj zadanego ... lista1 powinna być dłuższa.
Jochen Ritzel
Tak, ale wolałbym wymyślić albo rozwiązanie, które działa dla kontenerów o dowolnej długości, albo poddać się rozwiązaniom zaproponowanym powyżej.
pszeniczne
-2

Jestem za stary, żeby nie radzić sobie ze zrozumieniem list, więc:

import operator
list3 = reduce(operator.add, zip(list1, list2))
Tom Anderson
źródło