Pythonische Möglichkeit, zwei Listen abwechselnd zu kombinieren?

87

Ich habe zwei Listen, von denen die erste garantiert genau einen Artikel mehr enthält als die zweite . Ich würde gerne wissen, wie man am pythonischsten eine neue Liste erstellt, deren gerade Indexwerte aus der ersten Liste und deren ungerade Indexwerte aus der zweiten Liste stammen.

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

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

Das funktioniert, ist aber nicht schön:

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

Wie kann dies sonst noch erreicht werden? Was ist der pythonischste Ansatz?

Davidkammern
quelle
1
mögliches Duplikat des Wechsels zwischen Iteratoren in Python
Felix Kling
Kein Duplikat! Die akzeptierte Antwort im oben verlinkten Artikel erzeugt eine Liste von Tupeln, keine einzige zusammengeführte Liste.
Paul Sasik
@ Paul: Ja, die akzeptierte Antwort gibt nicht die vollständige Lösung. Lesen Sie die Kommentare und die anderen Antworten. Die Frage ist im Grunde die gleiche und die anderen Lösungen können hier angewendet werden.
Felix Kling
3
@ Felix: Ich bin respektvoll anderer Meinung. Es ist wahr, die Fragen sind in der gleichen Nachbarschaft, aber nicht wirklich Duplikate. Schauen Sie sich als vagen Beweis die möglichen Antworten hier an und vergleichen Sie sie mit der anderen Frage.
Paul Sasik
Schauen Sie sich diese an: stackoverflow.com/questions/7529376/…
wordsforthewise

Antworten:

105

Hier ist eine Möglichkeit, dies durch Schneiden zu tun:

>>> 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
quelle
3
Danke, Duncan. Mir war nicht klar, dass es möglich ist, beim Schneiden einen Schritt anzugeben. Was ich an diesem Ansatz mag, ist, wie natürlich er liest. 1. Erstellen Sie eine Liste mit der richtigen Länge. 2. Füllen Sie die geraden Indizes mit dem Inhalt von list1. 3. Füllen Sie die ungeraden Indizes mit dem Inhalt von list2. Die Tatsache, dass die Listen unterschiedlich lang sind, ist in diesem Fall kein Problem!
Davidchambers
2
Ich denke, es funktioniert nur, wenn len (Liste1) - len (Liste2) 0 oder 1 ist.
xan
1
Wenn die Listen die richtige Länge haben, funktioniert es. Wenn nicht, gibt die ursprüngliche Frage nicht an, welche Antwort erwartet wird. Es kann leicht geändert werden, um die meisten vernünftigen Situationen zu bewältigen: Wenn Sie beispielsweise möchten, dass zusätzliche Elemente ignoriert werden, schneiden Sie einfach die längere Liste ab, bevor Sie beginnen. Wenn Sie möchten, dass die zusätzlichen Elemente mit None verschachtelt sind, stellen Sie einfach sicher, dass das Ergebnis mit einigen weiteren None initialisiert wird. Wenn Sie zusätzliche Elemente am Ende hinzufügen möchten, ignorieren Sie sie und hängen Sie sie an.
Duncan
1
Auch ich war unklar. Der Punkt, den ich ansprechen wollte, ist, dass Duncans Lösung im Gegensatz zu vielen der aufgelisteten nicht durch die Tatsache kompliziert wird, dass die Listen ungleich lang sind. Sicher, es ist nur in einer begrenzten Anzahl von Situationen anwendbar, aber ich würde eine wirklich elegante Lösung, die in diesem Fall funktioniert, einer weniger eleganten Lösung vorziehen, die für zwei beliebige Listen funktioniert.
Davidchambers
1
Sie können (2 * len (list1) -1) anstelle von (len (list1) + len (list2)) verwenden, außerdem bevorzuge ich [0 :: 2] anstelle von [:: 2].
Lord British
50

Ein Rezept dafür finden Sie in der itertoolsDokumentation :

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

####BEARBEITEN:

Für eine Python-Version größer als 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.
quelle
Ich finde diesen Weg komplizierter als es sein muss. Es gibt eine bessere Option unten mit zip_longest.
Dubslow
@ Dubslow Für diesen speziellen Fall ist dies wahrscheinlich ein Overkill (wie ich in einem Kommentar an anderer Stelle erwähnt habe), es sei denn, Sie haben zufällig bereits Zugriff darauf. In anderen Situationen kann dies jedoch einige Vorteile haben. Dieses Rezept wurde sicherlich nicht für dieses Problem entwickelt, es löst es einfach.
David Z
1
Zu Ihrer Information sollten Sie das Rezept in der itertools Dokumentation verwenden, da es .next()nicht mehr funktioniert.
John W.
1
@ Johnw. man muss benutzen __next__. Es ist nicht in der Dokumentation geschrieben, daher habe ich eine Änderung der Antwort vorgeschlagen.
Marine Galantin
@Marine Ich würde es vorziehen, wenn Sie gerade das vorhandene Codebeispiel geändert hätten, aber ich kann das selbst beheben. Vielen Dank für Ihren Beitrag!
David Z
31

Dies sollte tun, was Sie wollen:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']
Mark Byers
quelle
Ihre erste Antwort hat mir sehr gut gefallen. Obwohl die Frage nicht perfekt beantwortet wurde, war es eine elegante Möglichkeit, zwei Listen gleicher Länge zusammenzuführen. Ich schlage vor, es zusammen mit der Einschränkung der Länge in Ihrer aktuellen Antwort beizubehalten.
Paul Sasik
1
Wenn list1 stattdessen ['f', 'o', 'o', 'd'] wäre, würde sein letztes Element ('d') nicht in der resultierenden Liste erscheinen (was angesichts der Besonderheiten der Frage völlig in Ordnung ist). Dies ist eine elegante Lösung!
Davidchambers
1
@ Mark yep (ich habe es positiv bewertet), nur auf die Unterschiede (und Einschränkungen, wenn andere Menschen ein anderes Verhalten wollen)
Cobbal
4
+1 für die Lösung des genannten Problems und einfach dafür :-) Ich dachte, so etwas wäre möglich. Ehrlich gesagt denke ich, dass die roundrobinFunktion für diese Situation eine Art Overkill ist.
David Z
1
Um mit Listen jeder Größe zu arbeiten, können Sie einfach das, was in den Iteratoren übrig ist, an das Ergebnis anhängen:list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
Panda-34
28
import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

Ich denke, das ist die pythonischste Art, es zu tun.

user942640
quelle
3
Warum ist dies nicht die akzeptierte Antwort? Dies ist die kürzeste und pythonischste und funktioniert mit unterschiedlichen Listenlängen!
Jairo Vadillo
5
Der Methodenname ist zip_longest nicht izip_longest
Jairo Vadillo
1
Das Problem dabei ist, dass der Standardfüllwert von zip_longest möglicherweise Nones überschreibt , die * in der Liste enthalten sein sollen. Ich werde in einer optimierten Version bearbeiten, um dies zu beheben
Dubslow
Hinweis: Dies wird zu Problemen führen , wenn die Listen - Elemente mit dem Wert enthält Falseoder sogar Dinge , die nur werden ausgewertet , wie Falsedurch die if-Ausdrucks, wie zum Beispiel eines 0oder eine leere Liste. Dies kann (teilweise) durch Folgendes vermieden werden : [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. Dies funktioniert natürlich immer noch nicht, wenn die Listen NoneElemente enthalten , die beibehalten werden müssen. In diesem Fall müssen Sie das fillvalueArgument von ändern zip_longest, wie Dubslow bereits vorgeschlagen hat.
der_herr_g
NoneDas Problem scheint verschwunden zu sein, zumindest seit Python 3.7.6 (ich weiß es nicht für ältere Versionen). Wenn alt_chaindefiniert als def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue)), wird list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))korrekt zurückgegeben [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].
23.
16

Ohne itertools und unter der Annahme, dass l1 1 Element länger als l2 ist:

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

Verwenden von itertools und unter der Annahme, dass Listen keine enthalten:

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')
Zart
quelle
Das ist meine Lieblingsantwort. Es ist so prägnant.
mbomb007
@ anishtain4 zip nimmt Elementpaare als Tupel aus Listen [(l1[0], l2[0]), (l1[1], l2[1]), ...]. sumverkettet Tupel miteinander: (l1[0], l2[0]) + (l1[1], l2[1]) + ...was zu verschachtelten Listen führt. Der Rest der einzeiligen Linie besteht nur aus einer Polsterung von l1 mit einem zusätzlichen Element, damit der Reißverschluss funktioniert, und schneide bis -1, um diese Polsterung zu entfernen.
Zart
izip_longest (zip_longest seit Python 3) benötigt kein + [0] Auffüllen, es füllt implizit None aus, wenn die Länge der Listen nicht übereinstimmt, während filter(None, ...(könnte boolstattdessen verwendet werden oder None.__ne__) falsche Werte entfernt werden, einschließlich 0, None und leere Zeichenfolgen. Der zweite Ausdruck entspricht also nicht unbedingt dem ersten.
Zart
Die Frage ist, wie haben Sie sumdas gemacht? Welche Rolle spielt dort das zweite Argument? In den Dokumentationen ist das zweite Argument start.
anishtain4
Der Standardwert für start ist 0, und Sie können 0+ (einige, Tupel) nicht ausführen. Daher wird start in leeres Tupel geändert.
Zart
12

Ich weiß, dass die Fragen zwei Listen betreffen, von denen eine einen Eintrag mehr als die andere enthält, aber ich dachte, ich würde dies für andere verwenden, die diese Frage möglicherweise finden.

Hier ist Duncans Lösung, die für zwei Listen unterschiedlicher Größe geeignet ist.

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

Dies gibt aus:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 
mhost
quelle
4

Hier ist ein Einzeiler, der das macht:

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

Jay
quelle
2
Das funktioniert richtig, scheint mir aber unelegant, da es so viel tut, um etwas so Einfaches zu erreichen. Ich sage nicht, dass dieser Ansatz ineffizient ist, sondern dass er nicht besonders leicht zu lesen ist.
Davidchambers
3

Wenn beide Listen gleich lang sind, können Sie Folgendes tun:

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

Da die erste Liste ein weiteres Element enthält, können Sie es post hoc hinzufügen:

[x for y in zip(list1, list2) for x in y] + [list1[-1]]
Jemand
quelle
1
^ Dies sollte die Antwort sein, Python ist in den letzten 10 Jahren pythonischer geworden
Tian
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
quelle
Seien Sie sehr vorsichtig, wenn Sie veränderbare Standardargumente verwenden. Dies gibt nur beim ersten Aufruf die richtige Antwort zurück, da lst danach für jeden Anruf wiederverwendet wird. Dies wäre besser geschrieben als lst = None ... wenn lst None ist: lst = [], obwohl ich keinen zwingenden Grund sehe, mich für diesen Ansatz gegenüber anderen hier aufgeführten zu entscheiden.
Davidchambers
lst wird innerhalb der Funktion definiert, ebenso wie eine lokale Variable. Das potenzielle Problem besteht darin, dass Liste1 und Liste2 jedes Mal wiederverwendet werden, wenn Sie die Funktion verwenden, selbst wenn Sie die Funktion mit unterschiedlichen Listen aufrufen. Siehe docs.python.org/tutorial/…
blokeley
1
@blokeley: falsch, würde wiederverwendet, wenn es kombiniert wurde (list1 = [...], list2 = [...])
killown
Als diese Lösung zum ersten Mal veröffentlicht wurde, wurde ihre erste Zeile gelesen def combine(list1, list2, lst=[]):, daher mein Kommentar. Als ich diesen Kommentar einreichte, hatte Killown die notwendigen Änderungen vorgenommen.
Davidchambers
2

Dieser basiert auf dem obigen Beitrag von Carlos Valiente mit der Option, Gruppen mehrerer Elemente zu wechseln und sicherzustellen, dass alle Elemente in der Ausgabe vorhanden sind:

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

Dadurch werden die Listen A und B nach Gruppen mit jeweils 3 Werten verschachtelt:

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

Meine Einstellung:

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
quelle
1

Hier ist ein Einzeiler mit Listenverständnis ohne andere Bibliotheken:

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

Hier ist ein anderer Ansatz, wenn Sie eine Änderung Ihrer ursprünglichen Liste1 durch Nebenwirkungen zulassen:

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

Könnte etwas spät sein, noch einen Python-Einzeiler zu kaufen. Dies funktioniert, wenn die beiden Listen gleich oder ungleich groß sind. Eine Sache, die nichts wert ist, ist, dass sie a und b modifiziert. Wenn es ein Problem ist, müssen Sie andere Lösungen verwenden.

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
quelle
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']
user3667349
quelle
0

Stoppt am kürzesten:

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

Sicher, das Auflösen der next / __ next__ -Methode kann schneller sein.

jwp
quelle
0

Das ist böse, funktioniert aber unabhängig von der Größe der Listen:

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

Mehrere Einzeiler, inspiriert von Antworten auf eine andere Frage :

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]
Worte dafür
quelle
0

Wie wäre es mit Numpy? Es funktioniert auch mit Strings:

import numpy as np

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

Ergebnis:

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

Eine funktionale und unveränderliche Alternative (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
quelle
-1

Ich würde das Einfache tun:

chain.from_iterable( izip( list1, list2 ) )

Es wird ein Iterator erstellt, ohne dass zusätzlicher Speicherbedarf entsteht.

Wheaties
quelle
1
Es ist wirklich einfach, aber es funktioniert nur mit Listen gleicher Länge!
Jochen Ritzel
Sie können es mit chain.from_iterable(izip(list1, list2), list1[len(list2):])für das hier gestellte Problem beheben ... list1 soll das längere sein.
Jochen Ritzel
Ja, aber ich möchte lieber entweder eine Lösung finden, die für Container beliebiger Länge funktioniert, oder den oben vorgeschlagenen Lösungen nachgeben.
Wheaties
-2

Ich bin zu alt, um mit Listenverständnissen fertig zu werden, also:

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