python - por - Comprensión para aplastar una secuencia de secuencias
diccionarios anidados en python (4)
Esta pregunta ya tiene una respuesta aquí:
- Hacer una lista plana de la lista de listas en Python 34 respuestas
Si tengo una secuencia de secuencias (tal vez una lista de tuplas), puedo usar itertools.chain () para aplanarla. Pero a veces siento que prefiero escribirlo como una comprensión. Simplemente no puedo entender cómo hacerlo. Aquí hay un caso muy elaborado:
Digamos que quiero cambiar los elementos de cada par en una secuencia. Yo uso una cadena como una secuencia aquí:
>>> from itertools import chain
>>> seq = ''012345''
>>> swapped_pairs = zip(seq[1::2], seq[::2])
>>> swapped_pairs
[(''1'', ''0''), (''3'', ''2''), (''5'', ''4'')]
>>> "".join(chain(*swapped_pairs))
''103254''
Utilizo zip en las partes pares e impares de la secuencia para intercambiar los pares. Pero termino con una lista de tuplas que ahora necesitan ser aplastadas. Entonces uso la cadena (). ¿Hay alguna manera de expresarlo con comprensión?
Si desea publicar su propia solución al problema básico de intercambiar elementos de los pares, adelante, voy a votar todo lo que me enseñe algo nuevo. Pero solo marcaré como aceptada una respuesta dirigida a mi pregunta, incluso si la respuesta es "No, no puede".
Con una comprensión? Bien...
>>> seq = ''012345''
>>> swapped_pairs = zip(seq[1::2], seq[::2])
>>> ''''.join(item for pair in swapped_pairs for item in pair)
''103254''
Lo más rápido que he encontrado es comenzar con una matriz vacía y ampliarla:
In [1]: a = [[''abc'', ''def''], [''ghi''],[''xzy'']]
In [2]: result = []
In [3]: extend = result.extend
In [4]: for l in a:
...: extend(l)
...:
In [5]: result
Out[5]: [''abc'', ''def'', ''ghi'', ''xzy'']
Esto es más del doble de rápido que el ejemplo en el intento de Alex Martelli: hacer una lista plana de la lista de listas en Python
$ python -mtimeit -s''l=[[1,2,3],[4,5,6], [7], [8,9]]*99'' ''[item for sublist in l for item in sublist]''
10000 loops, best of 3: 86.3 usec per loop
$ python -mtimeit -s''l=[[1,2,3],[4,5,6], [7], [8,9]]*99'' ''b = []'' ''extend = b.extend'' ''for sub in l:'' '' extend(sub)''
10000 loops, best of 3: 36.6 usec per loop
Se me ocurrió esto porque tenía la corazonada de que detrás de las escenas, extender asignaría la cantidad correcta de memoria para la lista, y probablemente use algún código de bajo nivel para mover los elementos. No tengo idea si esto es cierto, pero a quién le importa, es más rápido.
Por cierto, es solo una aceleración lineal:
$ python -mtimeit -s''l=[[1,2,3],[4,5,6], [7], [8,9]]'' ''b = []'' ''extend = b.extend'' ''for sub in l:'' '' extend(sub)''
1000000 loops, best of 3: 0.844 usec per loop
$ python -mtimeit -s''l=[[1,2,3],[4,5,6], [7], [8,9]]'' ''[item for sublist in l for item in sublist]''
1000000 loops, best of 3: 1.56 usec per loop
También puede usar el map(results.extend, a)
, pero esto es más lento ya que está construyendo su propia lista de Nones.
También le brinda algunos de los beneficios de no usar programación funcional. es decir
- puede extender una lista existente en lugar de crear una vacía,
- aún puede entender el código de un vistazo, minutos, días o incluso meses después.
Por cierto, probablemente sea mejor evitar las listas de comprensión. Los pequeños no son tan malos, pero en general las listas de comprensión no te ahorran mucho tipeo, pero a menudo son más difíciles de entender y muy difíciles de cambiar o refactorizar (¿alguna vez se ha visto una lista de tres niveles de comprensión?). Las pautas de codificación de Google desaconsejan, excepto en casos simples. Mi opinión es que solo son útiles en el código "descartable", es decir, código donde el autor no se preocupa por la legibilidad, o código que se sabe que nunca requiere mantenimiento futuro.
Compara estas dos formas de escribir lo mismo:
result = [item for sublist in l for item in sublist]
con este:
result = []
for sublist in l:
for item in sublist:
result.append(item)
YMMV, pero el primero me detuvo en seco y tuve que pensarlo. En el segundo, la anidación se hace evidente a partir de la sangría.
Puede usar reducir para lograr su objetivo:
In [6]: import operator
In [7]: a = [(1, 2), (2,3), (4,5)]
In [8]: reduce(operator.add, a, ())
Out[8]: (1, 2, 2, 3, 4, 5)
Esto devuelve una tupla en lugar de una lista porque los elementos en su lista original son tuplas que se concatenan. Pero puede crear fácilmente una lista a partir de eso y el método de unión también acepta tuplas.
Una lista de comprensión es, por cierto, no la herramienta adecuada para eso. Básicamente, una lista de comprensión crea una nueva lista al describir cómo deberían ser los elementos de esta lista. Desea reducir una lista de elementos a un solo valor.
>>> a = [(1, 2), (3, 4), (5, 6)]
>>> reduce(tuple.__add__, a)
>>> (1, 2, 3, 4, 5, 6)
O ser agnóstico sobre el tipo de secuencias internas (siempre que sean todas iguales):
>>> reduce(a[0].__class__.__add__, a)