sirve - ¿La mejor manera de intercalar dos o más listas en python?
para que sirve una lista en python (7)
Supongamos que tengo una lista:
l=[''a'',''b'',''c'']
Y su lista de sufijos:
l2 = [''a_1'', ''b_1'', ''c_1'']
Me gustaría que la salida deseada fuera:
out_l = [''a'',''a_1'',''b'',''b_2'',''c'',''c_3'']
El resultado es la versión intercalada de las dos listas anteriores.
Puedo escribir regularmente for
hacer esto, pero me pregunto si hay una forma más Pythonic (por ejemplo, usando la comprensión de lista o lambda) para hacerlo.
He intentado algo como esto:
list(map(lambda x: x[1]+''_''+str(x[0]+1), enumerate(a)))
# this only returns [''a_1'', ''b_2'', ''c_3'']
Además, ¿qué cambios se deberían hacer para el caso general, es decir, para 2 o más listas donde l2
no es necesariamente un derivado de l
?
yield
Puedes usar un generador para una solución elegante. En cada iteración, ceda dos veces: una vez con el elemento original y una vez con el elemento con el sufijo agregado.
El generador tendrá que estar agotado; eso se puede hacer añadiendo una list
al final.
def transform(l):
for i, x in enumerate(l, 1):
yield x
yield f''{x}_{i}'' # {}_{}''.format(x, i)
También puede volver a escribir esto utilizando el yield from
sintaxis para la delegación del generador:
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 está en versiones anteriores a python-3.6, reemplace f''{x}_{i}''
con ''{}_{}''.format(x, i)
.
Generalizando
Considere un escenario general donde tiene N listas del formulario:
l1 = [v11, v12, ...]
l2 = [v21, v22, ...]
l3 = [v31, v32, ...]
...
Que le gustaría intercalar. Estas listas no necesariamente se derivan unas de otras.
Para manejar las operaciones de intercalado con estas N listas, deberá iterar sobre pares:
def transformN(*args):
for vals in zip(*args):
yield from vals
out_l = transformN(l1, l2, l3, ...)
list.__setitem__
Lo recomendaría desde la perspectiva del rendimiento. Primero asigne espacio para una lista vacía, y luego asigne los elementos de la lista a sus posiciones apropiadas usando la asignación de la lista dividida. l
entra en los índices pares, y l''
( l
modificado) va en los índices impares.
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'']
Este es siempre el más rápido de mis tiempos (abajo).
Generalizando
Para manejar las listas N, asigna iterativamente a rebanadas.
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
Un enfoque funcional, similar a la solución de @chrisz. Construye pares usando zip
y luego itertools.chain
usando 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
es ampliamente considerado como el enfoque de aplanamiento de la lista pitónica.
Generalizando
Esta es la solución más sencilla de generalizar, y sospecho que la más eficiente para múltiples listas cuando N es grande.
list_of_lists = [l1, l2, ...]
out_l = list(chain.from_iterable(zip(*list_of_lists)))
Actuación
Echemos un vistazo a algunas pruebas de rendimiento para el caso simple de dos listas (una lista con su sufijo). Los casos generales no serán probados ya que los resultados varían ampliamente según los datos.
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()
Funciones
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))
]
Software
Sistema — Mac OS X High Sierra — Intel Core i7 a 2,4 GHz
Python — 3.6.0
IPython — 6.2.1
(Editado)
Usando la lista de comprensión:
[ 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))]
# l=[''b'', ''a'', ''d'', ''c'']
# output : [''b'', ''b_1'', ''a'', ''a_2'', ''d'', ''d_3'', ''c'', ''c_4'']
Aquí está mi implementación simple
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'']
Podrías usar zip
:
[val for pair in zip(l, [f''{k}_{j+1}'' for j, k in enumerate(l)]) for val in pair]
Salida:
[''a'', ''a_1'', ''b'', ''b_2'', ''c'', ''c_3'']
Puedes usar una lista de comprensión así:
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]
Salida:
[''a'', ''a_1'', ''b'', ''b_2'', ''c'', ''c_3'']
Opcional, método más corto:
[j for i, a in enumerate(l, 1) for j in [a, ''{}_{}''.format(a, i)]]
Si desea devolver [["a","a_1"],["b","b_2"],["c","c_3"]]
puede escribir
new_l=[[x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)]
Esto no es lo que quiere, en su lugar desea ["a","a_1"]+["b","b_2"]+["c","c_3"]
. Esto se puede hacer a partir del resultado de la operación anterior utilizando sum()
; ya que está sumando listas, necesita agregar la lista vacía como un argumento para evitar un error. Así que eso da
new_l=sum(([x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)),[])
No sé cómo esto se compara con la velocidad (probablemente no sea así), pero me resulta más fácil entender lo que está pasando que las otras respuestas basadas en listas de comprensión.
Una solución muy simple:
out_l=[]
for i,x in enumerate(l,1):
out_l.extend([x,f"{x}_{i}"])