web-dev-qa-db-fra.com

Le meilleur moyen d'entrelacer deux ou plusieurs listes en python?

Supposons que j'ai une liste:

l=['a','b','c']

Et sa liste de suffixes:

l2 = ['a_1', 'b_1', 'c_1']

J'aimerais que le résultat souhaité soit:

out_l = ['a','a_1','b','b_2','c','c_3']

Le résultat est la version entrelacée des deux listes ci-dessus.

Je peux écrire une boucle for régulière pour y parvenir, mais je me demande s’il existe une méthode plus pythonique (par exemple, en utilisant la compréhension par liste ou lambda).

J'ai essayé quelque chose comme ça:

list(map(lambda x: x[1]+'_'+str(x[0]+1), enumerate(a)))
# this only returns ['a_1', 'b_2', 'c_3']

En outre, quels changements faudrait-il apporter dans le cas général, c’est-à-dire pour deux listes ou plus où l2 n’est pas nécessairement un dérivé de l?

26
user1330974

yield

Vous pouvez utiliser un générateur pour une solution élégante. A chaque itération, donnez deux fois— une fois avec l'élément d'origine et une fois avec l'élément avec le suffixe ajouté.

Le générateur devra être épuisé; cela peut être fait en misant sur un appel list à la fin.

def transform(l):
    for i, x in enumerate(l, 1):
        yield x
        yield f'{x}_{i}'  # {}_{}'.format(x, i)

Vous pouvez également réécrire ceci en utilisant la syntaxe yield from pour la délégation du générateur:

def transform(l):
    for i, x in enumerate(l, 1):
        yield from (x, f'{x}_{i}') # (x, {}_{}'.format(x, i))

out_l = list(transform(l))
print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Si vous utilisez une version antérieure à python-3.6, remplacez f'{x}_{i}' par '{}_{}'.format(x, i).

Généraliser
Considérons un scénario général dans lequel vous avez N listes de la forme:

l1 = [v11, v12, ...]
l2 = [v21, v22, ...]
l3 = [v31, v32, ...]
...

Que vous voudriez entrelacer. Ces listes ne sont pas nécessairement dérivées les unes des autres.

Pour gérer les opérations d'entrelacement avec ces N listes, vous devez effectuer une itération par paires:

def transformN(*args):
    for vals in Zip(*args):
        yield from vals

out_l = transformN(l1, l2, l3, ...)

list.__setitem__ en tranches

Je recommanderais ceci du point de vue de la performance. Commencez par allouer de l'espace pour une liste vide, puis affectez les éléments de la liste à leurs emplacements appropriés à l'aide de l'affectation de la liste par tranches. l va dans les index pairs, et l' (l modifié) va dans les index impairs. 

out_l = [None] * (len(l) * 2)
out_l[::2] = l
out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]  # [{}_{}'.format(x, i) ...]

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

C'est toujours le plus rapide de mes timings (ci-dessous).

Généraliser
Pour manipuler N listes, attribuez itérativement des tranches. 

list_of_lists = [l1, l2, ...]

out_l = [None] * len(list_of_lists[0]) * len(list_of_lists)
for i, l in enumerate(list_of_lists):
    out_l[i::2] = l

Zip + chain.from_iterable

Une approche fonctionnelle, similaire à la solution de @chrisz. Construisez des paires en utilisant Zip, puis aplatissez-la en utilisant itertools.chain.

from itertools import chain
# [{}_{}'.format(x, i) ...]
out_l = list(chain.from_iterable(Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))) 

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

iterools.chain est largement considéré comme l’approche d’aplatissement de liste Pythonic.

Généraliser
C’est la solution la plus simple à généraliser, et je soupçonne qu’elle est la plus efficace pour les listes multiples lorsque N est grand.

list_of_lists = [l1, l2, ...]
out_l = list(chain.from_iterable(Zip(*list_of_lists)))

Performance

Jetons un coup d'oeil à quelques tests de performance pour le cas simple de deux listes (une liste avec son suffixe). Les cas généraux ne seront pas testés car les résultats varient largement avec les données.

from timeit import timeit

import pandas as pd
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['ajax1234', 'cs0', 'cs1', 'cs2', 'cs3', 'chrisz', 'sruthiV'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        l = ['a', 'b', 'c', 'd'] * c
        stmt = '{}(l)'.format(f)
        setp = 'from __main__ import l, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=50)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show()

 enter image description here

Les fonctions

def ajax1234(l):
    return [
        i for b in [[a, '{}_{}'.format(a, i)] 
        for i, a in enumerate(l, start=1)] 
        for i in b
    ]

def cs0(l):
    # this is in Ajax1234's answer, but it is my suggestion
    return [j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]

def cs1(l):
    def _cs1(l):
        for i, x in enumerate(l, 1):
            yield x
            yield f'{x}_{i}'

    return list(_cs1(l))

def cs2(l):
    out_l = [None] * (len(l) * 2)
    out_l[::2] = l
    out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]

    return out_l

def cs3(l):
    return list(chain.from_iterable(
        Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))
    )

def chrisz(l):
    return [
        val 
        for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) 
        for val in pair
    ]

def sruthiV(l):
    return [ 
        l[int(i / 2)] + "_" + str(int(i / 2) + 1) if i % 2 != 0 else l[int(i/2)] 
        for i in range(0,2*len(l))
    ]

Logiciel

Système — Mac OS X High Sierra — Intel Core i7 à 2,4 GHz
Python — 3.6.0
IPython — 6.2.1 

57
coldspeed

Vous pouvez utiliser une liste de compréhension comme suit:

l=['a','b','c']
new_l = [i for b in [[a, '{}_{}'.format(a, i)] for i, a in enumerate(l, start=1)] for i in b]

Sortie:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Méthode optionnelle plus courte:

[j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]
6
Ajax1234

Vous pouvez utiliser Zip :

[val for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) for val in pair]

Sortie:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
5
user3483203

Voici ma simple implémentation

l=['a','b','c']
# generate new list with the indices of the original list
new_list=l + ['{0}_{1}'.format(i, (l.index(i) + 1)) for i in l]
# sort the new list in ascending order
new_list.sort()
print new_list
# Should display ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
2
Isaac Boakye

Si vous voulez renvoyer [["a","a_1"],["b","b_2"],["c","c_3"]] vous pouvez écrire 

new_l=[[x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)]

Ce n'est pas ce que vous voulez, mais plutôt ["a","a_1"]+["b","b_2"]+["c","c_3"]. Cela peut être fait à partir du résultat de l'opération ci-dessus en utilisant sum(); puisque vous additionnez des listes, vous devez ajouter la liste vide comme argument pour éviter les erreurs. Donc ça donne

new_l=sum(([x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)),[])

Je ne sais pas comment cela se compare en termes de vitesse (probablement pas bien), mais je trouve qu'il est plus facile de comprendre ce qui se passe que les autres réponses basées sur la compréhension de liste.

0
Especially Lime

Une solution très simple:

out_l=[]
for i,x in enumerate(l,1):
    out_l.extend([x,f"{x}_{i}"])
0
kantal