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
?
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 tranchesJe 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)))
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()
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))
]
Système — Mac OS X High Sierra — Intel Core i7 à 2,4 GHz
Python — 3.6.0
IPython — 6.2.1
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)]]
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']
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']
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.
Une solution très simple:
out_l=[]
for i,x in enumerate(l,1):
out_l.extend([x,f"{x}_{i}"])