2009-07-02 14 views
52

chicos. Intento encontrar la solución más elegante para un problema y me pregunto si Python tiene algo incorporado para lo que estoy tratando de hacer.comprensión de la lista de Python; ¿Comprimir una lista de listas?

Lo que estoy haciendo es esto. Tengo una lista, A, y tengo una función f que toma un elemento y devuelve una lista. Puedo usar una lista de comprensión para convertir todo en A como tal;

[f(a) for a in A] 

Pero esto arroja una lista de listas;

[a1,a2,a3] => [[b11,b12],[b21,b22],[b31,b32]] 

Lo que realmente quiero es conseguir la lista aplanado;

[b11,b12,b21,b22,b31,b32] 

Ahora, otros idiomas tienen que; tradicionalmente se llama flatmap en lenguajes de programación funcionales, y .Net lo llama SelectMany. ¿Python tiene algo similar? ¿Hay una forma ordenada de asignar una función sobre una lista y aplanar el resultado?

El problema real que estoy tratando de resolver es esto; comenzando con una lista de directorios, encuentre todos los subdirectorios. asi que;

import os 
dirs = ["c:\\usr", "c:\\temp"] 
subs = [os.listdir(d) for d in dirs] 
print subs 

currentliy me da una lista de listas de tareas, pero realmente quiero una lista.

Respuesta

74

Puede haber anidado iteraciones en una lista única de comprensión:

[filename for path in dirs for filename in os.listdir(path)] 
+25

Aunque inteligente, es difícil de entender y poco legible. –

+1

Realmente no responde la pregunta como se le preguntó. Esto es más bien una solución para no encontrar el problema en primer lugar. ¿Qué pasa si ya tiene una lista de listas? Por ejemplo, ¿qué pasa si su lista de listas es un resultado de la función de mapa del módulo de multiprocesamiento? Quizás la solución de itertools o la solución de reducción sea la mejor. – Dave31415

+8

Dave31415: '[item for list in listoflists para item in list]' – rampion

3

Usted podría intentar itertools.chain(), así:

import itertools 
import os 
dirs = ["c:\\usr", "c:\\temp"] 
subs = list(itertools.chain(*[os.listdir(d) for d in dirs])) 
print subs 

itertools.chain() un iterador, por lo tanto, el paso a list().

5
subs = [] 
map(subs.extend, (os.listdir(d) for d in dirs)) 

(respuesta, pero de las hormigas es mejor; 1 para él)

+0

Usando reducir (o suma, lo que le ahorra muchos personajes y una importación ;-) de esto es simplemente incorrecto - usted guarda inútilmente arrojando viejas listas a hacer uno nuevo para cada d. @Ants tiene la respuesta correcta (inteligente de @Steve para aceptarlo). –

+0

@Alex: Buen punto. Fijo. – RichieHindle

+0

No se puede decir en general que esta es una mala solución. Depende de si el rendimiento es incluso un problema. Simple es mejor a menos que haya una razón para optimizar. Es por eso que el método de reducción podría ser mejor para muchos problemas. Por ejemplo, tiene una función lenta que produce una lista de unos cientos de objetos.Desea acelerarlo mediante el uso de la función 'mapa' de multiprocesamiento. Entonces usted crea 4 procesos y usa reducirlos para mapearlos planos. En este caso, la función de reducción es fina y muy legible. Dicho esto, es bueno que señale por qué esto puede ser subóptimo. Pero no siempre es subóptimo. – Dave31415

37

Usted puede encontrar una buena respuesta en itertools' recipes:

def flatten(listOfLists): 
    return list(chain.from_iterable(listOfLists)) 

(Nota: requiere Python 2.6+)

+0

El mismo enfoque se puede usar para definir flatmap, tal como lo propuso [esta respuesta] (https://stackoverflow.com/a/20037408/1048186) y esta [publicación de blog externa] (http://naiquevin.github.io /a-look-at-some-of-pythons-useful-itertools.html) –

3

Google me llevó siguiente solución:

def flatten(l): 
    if isinstance(l,list): 
     return sum(map(flatten,l)) 
    else: 
     return l 
+2

Sería un poco mejor si manejara expresiones de generador, y sería mucho mejor si explicaras cómo usarlo ... – ephemient

14

Se podía hacer lo sencillo:

subs = [] 
for d in dirs: 
    subs.extend(os.listdir(d)) 
+0

Sí, está bien (aunque no tan bueno como @Ants ') así que estoy ¡dándole un +1 para honrar su simplicidad! –

10

Puede concatenar listas utilizando el operador de suma normal:

>>> [1, 2] + [3, 4] 
[1, 2, 3, 4] 

La función integrada de sum agregará los números en una secuencia y puede comenzar opcionalmente de un valor específico:

>>> sum(xrange(10), 100) 
145 

Combine lo anterior para aplanar una lista de listas:

>>> sum([[1, 2], [3, 4]], []) 
[1, 2, 3, 4] 

ahora puede definir su flatmap:

>>> def flatmap(f, seq): 
... return sum([f(s) for s in seq], []) 
... 
>>> flatmap(range, [1,2,3]) 
[0, 0, 1, 0, 1, 2] 

Edición: Acabo de ver la crítica en los comentarios de another answer y creo que es correcto que Python compilará innecesariamente y que la basura recopile muchas listas más pequeñas con esta solución. Así que lo mejor que se puede decir de él es que es muy simple y concisa, si estás acostumbrado a la programación funcional :-)

+0

Excelente respuesta – Phil

45
>>> listOfLists = [[1, 2],[3, 4, 5], [6]] 
>>> reduce(list.__add__, listOfLists) 
[1, 2, 3, 4, 5, 6] 

supongo itertools la solución es más eficiente que esto, pero esto se siente muy pitónico y evita tener que importar una biblioteca solo por el simple hecho de una operación de lista única.

+3

Esta es definitivamente la mejor solución. –

7
import itertools 
x=[['b11','b12'],['b21','b22'],['b31']] 
y=list(itertools.chain(*x)) 
print y 

itertools trabajarán de python2.3 y una mayor

14

La pregunta propuesta flatmap. Se proponen algunas implementaciones pero pueden crear listas intermedias innecesarias. Aquí hay una implementación basada en iteradores.

def flatmap(func, *iterable): 
    return itertools.chain.from_iterable(map(func, *iterable)) 

In [148]: list(flatmap(os.listdir, ['c:/mfg','c:/Intel'])) 
Out[148]: ['SPEC.pdf', 'W7ADD64EN006.cdr', 'W7ADD64EN006.pdf', 'ExtremeGraphics', 'Logs'] 

En Python 2.x, utilice itertools.map en lugar de map.

0
If listA=[list1,list2,list3] 
flattened_list=reduce(lambda x,y:x+y,listA) 

Esto hará.

+0

Esta es una solución muy ineficiente si las sublistas son grandes. El operador '+' entre dos listas es O (n + m) –

1
def flat_list(arr): 
    send_back = [] 
    for i in arr: 
     if type(i) == list: 
      send_back += flat_list(i) 
     else: 
      send_back.append(i) 
    return send_back 
1

Puede utilizar pyxtension:

from pyxtension.streams import stream 
stream([ [1,2,3], [4,5], [], [6] ]).flatMap() == range(7) 
Cuestiones relacionadas