Verständnis der Python-Liste; eine Liste von Listen komprimieren?

79

Jungs. Ich versuche, die eleganteste Lösung für ein Problem zu finden, und frage mich, ob in Python etwas für das integriert ist, was ich versuche.

Was ich tue ist das. Ich habe eine Liste Aund eine Funktion, fdie ein Element aufnimmt und eine Liste zurückgibt. Ich kann ein Listenverständnis verwenden, um alles Aso umzuwandeln .

[f(a) for a in A]

Dies gibt jedoch eine Liste von Listen zurück.

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

Was ich wirklich will, ist, die abgeflachte Liste zu bekommen;

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

Jetzt haben es andere Sprachen; Es wird traditionell flatmapin funktionalen Programmiersprachen aufgerufen , und .Net nennt es SelectMany. Hat Python etwas Ähnliches? Gibt es eine gute Möglichkeit, eine Funktion einer Liste zuzuordnen und das Ergebnis zu reduzieren?

Das eigentliche Problem, das ich zu lösen versuche, ist das folgende; Beginnen Sie mit einer Liste von Verzeichnissen und suchen Sie alle Unterverzeichnisse. damit;

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

Aktuell gibt mir eine Liste von Listen, aber ich möchte wirklich eine Liste.

Steve Cooper
quelle

Antworten:

117

Sie können verschachtelte Iterationen in einem einzigen Listenverständnis haben:

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

Das entspricht (zumindest funktional):

filenames = []
for path in dirs:
    for filename in os.listdir(path):
        filenames.append(filename)
Ameisen Aasma
quelle
65
Obwohl klug, ist das schwer zu verstehen und nicht sehr lesbar.
Curtis Yallop
2
Beantwortet die gestellte Frage nicht wirklich. Dies ist eher eine Problemumgehung, um das Problem überhaupt nicht zu finden. Was ist, wenn Sie bereits eine Liste von Listen haben? Was ist zum Beispiel, wenn Ihre Liste von Listen ein Ergebnis der Kartenfunktion des Multiprozessor-Moduls ist? Vielleicht ist die itertools-Lösung oder die Reduktionslösung am besten.
Dave31415
22
Dave31415:[ item for list in listoflists for item in list ]
Rampion
9
"Lesbarkeit" ist ein subjektives Urteil. Ich finde diese Lösung gut lesbar.
Reb.Cabin
9
Ich dachte, es sei auch lesbar, bis ich die Reihenfolge der Begriffe sah ... :(
cz
83
>>> from functools import reduce
>>> listOfLists = [[1, 2],[3, 4, 5], [6]]
>>> reduce(list.__add__, listOfLists)
[1, 2, 3, 4, 5, 6]

Ich vermute, die itertools-Lösung ist effizienter als diese, aber das fühlt sich sehr pythonisch an.

In Python 2 wird vermieden, dass eine Bibliothek nur für eine einzelne Listenoperation importiert werden muss (da dies reduceeine integrierte Funktion ist).

julianisch
quelle
5
Dies ist definitiv die beste Lösung.
Connor Doyle
Was soll ich anrufen , um zu importieren reduce, ist es pandas, scipyoder functools?
Sajjon
1
In Python 2 ist es integriert. Für Python3 ist die functoolsVersion dieselbe.
Julian
Verdammt ! Das ist Genie
Tilak Maddy
28

Die vorgeschlagene Frage flatmap. Einige Implementierungen werden vorgeschlagen, aber möglicherweise müssen keine Zwischenlisten erstellt werden. Hier ist eine Implementierung, die auf Iteratoren basiert.

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']

Verwenden Sie itertools.mapin Python 2.x anstelle von map.

Wai Yip Tung
quelle
18

Sie könnten einfach das Unkomplizierte tun:

subs = []
for d in dirs:
    subs.extend(os.listdir(d))
Anon
quelle
Ja, das ist in Ordnung (wenn auch nicht ganz so gut wie bei @Ants), also gebe ich ihm eine +1, um seine Einfachheit zu würdigen!
Alex Martelli
16

Sie können Listen mit dem normalen Additionsoperator verketten:

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

Die integrierte Funktion sumfügt die Zahlen in einer Sequenz hinzu und kann optional von einem bestimmten Wert ausgehen:

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

Kombinieren Sie das Obige, um eine Liste von Listen zu reduzieren:

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

Sie können jetzt Folgendes definieren flatmap:

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

Bearbeiten: Ich habe gerade die Kritik in den Kommentaren für eine andere Antwort gesehen und ich denke, es ist richtig, dass Python mit dieser Lösung unnötig viele kleinere Listen erstellen und Müll sammeln wird. Das Beste, was man dazu sagen kann, ist, dass es sehr einfach und prägnant ist, wenn man an funktionale Programmierung gewöhnt ist :-)

Martin Geisler
quelle
Dies ist die coolste Art, Listen zu reduzieren.
Ulrich Scheller
12
import itertools
x=[['b11','b12'],['b21','b22'],['b31']]
y=list(itertools.chain(*x))
print y

itertools funktionieren ab python2.3 und höher

Dunkle Nacht
quelle
8
subs = []
map(subs.extend, (os.listdir(d) for d in dirs))

(aber Ants 'Antwort ist besser; +1 für ihn)

RichieHindle
quelle
Die Verwendung von redu (oder sum, wodurch Sie viele Zeichen und einen Import sparen ;-) ist einfach falsch - Sie werfen immer wieder alte Listen weg, um für jedes d eine neue zu erstellen. @Ants hat die richtige Antwort (klug von @Steve, um es zu akzeptieren!).
Alex Martelli
Man kann im Allgemeinen nicht sagen, dass dies eine schlechte Lösung ist. Es kommt darauf an, ob Leistung überhaupt ein Problem ist. Einfach ist besser, es sei denn, es gibt einen Grund zur Optimierung. Aus diesem Grund kann die Reduzierungsmethode für viele Probleme am besten geeignet sein. Sie haben beispielsweise eine langsame Funktion, die eine Liste mit einigen hundert Objekten erstellt. Sie möchten es beschleunigen, indem Sie die Mehrfachverarbeitungsfunktion 'map' verwenden. Sie erstellen also 4 Prozesse und verwenden Reduzieren, um sie flach abzubilden. In diesem Fall ist die Reduzierungsfunktion in Ordnung und gut lesbar. Trotzdem ist es gut, dass Sie darauf hinweisen, warum dies suboptimal sein kann. Es ist aber nicht immer suboptimal.
Dave31415
4

Sie könnten es so versuchen itertools.chain():

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

itertools.chain()Gibt einen Iterator zurück, daher die Übergabe an list().

Steef
quelle
3

Google brachte mir die nächste Lösung:

def flatten(l):
   if isinstance(l,list):
      return sum(map(flatten,l))
   else:
      return l
Vestel
quelle
2
Wäre ein bisschen besser, wenn es auch mit Generatorausdrücken
umgehen
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
Vinod Kumar
quelle
1

Sie können Pyxtension verwenden :

from pyxtension.streams import stream
stream([ [1,2,3], [4,5], [], [6] ]).flatMap() == range(7)
asu
quelle
0
If listA=[list1,list2,list3]
flattened_list=reduce(lambda x,y:x+y,listA)

Das wird es tun.

Prabhudatta Das
quelle
Dies ist eine sehr ineffiziente Lösung, wenn die Unterlisten groß sind. Der +Operator zwischen zwei Listen ist O (n + m)
juanpa.arrivillaga