2009-07-02 14 views
52

les gars. J'essaie de trouver la solution la plus élégante à un problème et me demande si python a quelque chose de intégré pour ce que j'essaie de faire.compréhension de la liste python; compresser une liste de listes?

Ce que je fais est ceci. J'ai une liste, A, et j'ai une fonction f qui prend un article et renvoie une liste. Je peux utiliser une compréhension de liste pour tout convertir en A comme ça;

[f(a) for a in A] 

Mais cela retourne une liste de listes;

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

Ce que je veux vraiment, c'est obtenir la liste aplatie;

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

Maintenant, d'autres langues l'ont; il est traditionnellement appelé flatmap dans les langages de programmation fonctionnels, et .Net l'appelle SelectMany. Est-ce que python a quelque chose de similaire? Existe-t-il une manière ordonnée de mapper une fonction sur une liste et d'aplatir le résultat?

Le problème que j'essaie de résoudre est le suivant: en commençant par une liste de répertoires, trouvez tous les sous-répertoires. alors;

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

currentliy me donne une liste-des-listes, mais je veux vraiment une liste.

Répondre

74

Vous pouvez avoir des itérations imbriquées dans une seule compréhension de liste:

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

intelligent, c'est difficile à comprendre et pas très lisible. –

+1

Ne répond pas vraiment à la question posée. C'est plutôt une solution de contournement pour ne pas rencontrer le problème en premier lieu. Que faire si vous avez déjà une liste de listes. Par exemple, que se passe-t-il si votre liste de listes est le résultat de la fonction de carte du module multitraitement? Peut-être que la solution d'itertools ou la solution de réduction est la meilleure. – Dave31415

+8

Dave31415: '[élément de liste dans listoflists pour élément dans la liste]' – rampion

3

Vous pouvez essayer itertools.chain(), comme ceci:

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

itertools.chain() retourne un itérateur, d'où le passage à list().

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

(mais la réponse de fourmis est mieux, +1 pour lui)

+0

L'utilisation de réduire (ou somme, qui vous permet d'économiser beaucoup de caractères et une importation ;-) pour cela est tout simplement faux - vous continuez à jeter inutilement loin les anciennes listes à faire un nouveau pour chaque d. @Ants a la bonne réponse (intelligente de @Steve pour l'accepter!). –

+0

@Alex: Bon point. Fixé. – RichieHindle

+0

Vous ne pouvez pas dire en général que c'est une mauvaise solution. Cela dépend si la performance est même un problème. Simple est préférable à moins qu'il y ait une raison d'optimiser. C'est pourquoi la méthode de réduction pourrait être la meilleure pour de nombreux problèmes. Par exemple, vous avez une fonction lente qui produit une liste de quelques centaines d'objets.Vous voulez l'accélérer en utilisant la fonction 'carte' multi-traitement. Donc, vous créez 4 processus et utilisez réduire pour les cartographier à plat. Dans ce cas, la fonction de réduction est très bien et très lisible. Cela dit, il est bon que vous indiquiez pourquoi cela peut être sous-optimal. Mais ce n'est pas toujours sous-optimal. – Dave31415

37

Vous pouvez trouver une bonne réponse dans itertools' recipes:

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

(Note: nécessite Python 2.6+)

+0

La même approche peut être utilisée pour définir flatmap, comme proposé par [cette réponse] (https://stackoverflow.com/a/20037408/1048186) et ce [post de blog externe] (http://naiquevin.github.io /a-look-at-some-of-pythons-useful-itertools.html) –

3

Google m'a apporté la solution suivante:

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

Serait un peu meilleur s'il traitait aussi des expressions de générateur, et serait beaucoup mieux si vous expliquiez comment l'utiliser ... – ephemient

14

Vous pouvez simplement faire le simple:

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

Oui, c'est bien (mais pas aussi bon que @Ants) donc je suis en lui donnant un +1 pour honorer sa simplicité! –

10

Vous pouvez concaténer des listes à l'aide de l'opérateur d'ajout normal:

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

La fonction intégrée sum ajoutera les numéros dans une séquence et peut éventuellement commencer à partir d'une valeur spécifique:

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

Combiner ci-dessus pour aplatir une liste de listes:

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

Vous pouvez maintenant définir votre flatmap:

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

Modifier: Je viens de voir la critique dans les commentaires pour another answer et je suppose qu'il est correct que Python va inutilement construire et garbage collecter beaucoup de petites listes avec cette solution. Donc, la meilleure chose qui peut être dit à ce sujet est qu'il est très simple et concise si vous êtes habitué à la programmation fonctionnelle :-)

+0

Excellente réponse – Phil

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

Je devine que la solution itertools est plus efficace que cela, mais cela semble très pythonique et évite d'avoir à importer une bibliothèque juste pour le plaisir d'une seule opération de liste.

+3

C'est certainement la meilleure solution –

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

itertools travailleront de python2.3 et plus

14

La question proposée flatmap. Certaines mises en œuvre sont proposées, mais elles peuvent ne pas créer de listes intermédiaires inutiles. Voici une implémentation basée sur les itérateurs.

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, utilisez itertools.map en place de map.

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

Cela fera l'affaire.

+0

C'est une solution très inefficace si les sous-listes sont grandes L'opérateur '+ 'entre deux listes est 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

Vous pouvez utiliser pyxtension:

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