104 votes

Méthode pythonique pour combiner deux listes de manière alternée ?

J'ai deux listes, dont le premier est assuré de contenir exactement un élément de plus que le second . J'aimerais connaître la manière la plus pythonique de créer une nouvelle liste dont les valeurs d'indice pair proviennent de la première liste et dont les valeurs d'indice impair proviennent de la deuxième liste.

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

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

Cela fonctionne, mais ce n'est pas joli :

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

Comment y parvenir autrement ? Quelle est l'approche la plus pythique ?

2 votes

Duplicata possible de Alternance d'itérateurs en Python

0 votes

Pas un doublon ! La réponse acceptée dans l'article ci-dessus produit une liste de tuples, et non une liste unique et fusionnée.

0 votes

@Paul : Oui, la réponse acceptée ne donne pas la solution complète. Lisez les commentaires et les autres réponses. La question est fondamentalement la même et les autres solutions peuvent être appliquées ici.

136voto

Duncan Points 25356

Voici une façon de le faire en tranchant :

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

5 votes

Merci, Duncan. Je n'avais pas réalisé qu'il était possible de spécifier une étape lors du découpage. Ce que j'aime dans cette approche, c'est qu'elle se lit naturellement. 1. Faire une liste de la bonne longueur. 2. Peupler les index pairs avec le contenu de la liste1. 3. Remplir les index impairs avec le contenu de la liste2. Le fait que les listes soient de longueurs différentes n'est pas un problème dans ce cas !

2 votes

Je pense que cela ne fonctionne que lorsque len(list1) - len(list2) est 0 ou 1.

1 votes

Si les listes sont d'une longueur appropriée, cela fonctionne, sinon la question originale ne précise pas quelle réponse est attendue. Elle peut être facilement modifiée pour gérer la plupart des situations raisonnables : par exemple, si vous voulez que les éléments supplémentaires soient ignorés, il suffit de réduire la liste la plus longue avant de commencer ; si vous voulez que les éléments supplémentaires soient intercalés avec des None, assurez-vous simplement que le résultat est initialisé avec d'autres None ; si vous voulez que les éléments supplémentaires soient simplement ajoutés à la fin, faites comme pour les ignorer, puis ajoutez-les.

52voto

David Z Points 49476

Il y a une recette pour cela dans le itertools documentation :

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 :

Pour les versions de python supérieures à 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))

0 votes

Je trouve que c'est beaucoup plus compliqué que ça ne doit l'être. Il y a une meilleure option ci-dessous en utilisant zip_longest .

0 votes

@Dubslow Pour ce cas particulier, oui, c'est probablement exagéré (comme je l'ai mentionné dans un autre commentaire), à moins que vous n'y ayez déjà accès. Elle pourrait toutefois présenter des avantages dans d'autres situations. Cette recette n'a certainement pas été conçue pour ce problème, elle se trouve simplement à le résoudre.

1 votes

Fyi vous devriez utiliser la recette dans la section itertools documentation parce que .next() ne fonctionne plus.

31voto

Mark Byers Points 318575

En Python 2, cela devrait faire ce que vous voulez :

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

0 votes

J'ai beaucoup aimé votre première réponse. Même si elle ne répondait pas parfaitement à la question, elle constituait un moyen élégant de fusionner deux listes de même longueur. Je suggère de la conserver, ainsi que la mise en garde concernant la longueur, dans votre réponse actuelle.

0 votes

Cette réponse est similaire à celle de David, mais elle est strictement entrelacée (elle s'arrêtera plutôt que de continuer avec les listes suivantes).

0 votes

@cobbal : Ce n'est pas ce qu'il voulait ?

20voto

Zart Points 636

Sans itertools et en supposant que l1 est 1 élément plus long que l2 :

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

Dans python 2, en utilisant itertools et en supposant que les listes ne contiennent pas None :

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

0 votes

C'est ma réponse préférée. C'est tellement concis.

0 votes

@anishtain4 zip prend des paires d'éléments comme tuples de listes, [(l1[0], l2[0]), (l1[1], l2[1]), ...] . sum concatène les tuples ensemble : (l1[0], l2[0]) + (l1[1], l2[1]) + ... ce qui donne des listes entrelacées. Le reste de la ligne n'est qu'un remplissage de l1 avec un élément supplémentaire pour que la fermeture éclair fonctionne et que l'on coupe jusqu'à -1 pour se débarrasser de ce remplissage.

0 votes

Izip_longest (zip_longest depuis python 3) n'a pas besoin de remplissage +[0], il remplit implicitement None lorsque les longueurs des listes ne correspondent pas, alors que filter(None, ... (pourrait utiliser bool à la place, ou None.__ne__ ) supprime les valeurs fausses, y compris 0, None et les chaînes vides, de sorte que la deuxième expression n'est pas strictement équivalente à la première.

13voto

mhost Points 1320

Je sais que la question porte sur deux listes dont l'une contient un élément de plus que l'autre, mais je me suis dit que je mettrais cela à la disposition des autres qui pourraient trouver cette question.

Voici La solution de Duncan adapté pour travailler avec deux listes de tailles différentes.

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

Ces sorties :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X