Python: Der schnellste Weg, um eine Liste mit n Listen zu erstellen

97

Also habe ich mich gefragt, wie ich am besten eine Liste mit leeren Listen erstellen kann:

[[],[],[]...]

Aufgrund der Funktionsweise von Python mit Listen im Speicher funktioniert dies nicht:

[[]]*n

Dadurch wird zwar erstellt, [[],[],...]aber jedes Element ist dieselbe Liste:

d = [[]]*n
d[0].append(1)
#[[1],[1],...]

So etwas wie ein Listenverständnis funktioniert:

d = [[] for x in xrange(0,n)]

Dies verwendet jedoch die Python-VM zum Schleifen. Gibt es eine Möglichkeit, eine implizite Schleife zu verwenden (wobei davon profitiert wird, dass sie in C geschrieben ist)?

d = []
map(lambda n: d.append([]),xrange(0,10))

Das ist eigentlich langsamer. :(

Munchybunch
quelle
1
Ich wäre überrascht, wenn es etwas wesentlich schnelleres gibt als d = [[] for x in xrange(0,n)]. Sie müssen entweder eine explizite Schleife in Python ausführen oder eine Python-Funktion / Lambda wiederholt aufrufen (was langsamer sein sollte). Aber ich hoffe immer noch, dass jemand etwas veröffentlicht, das zeigt, dass ich falsch liege :).
MAK
1
timeitWas haben Sie gelernt, als Sie diese gemessen haben ?
S.Lott
Ich habe gerade bestätigt, dass dies map(lambda x: [], xrange(n))langsamer ist als ein Listenverständnis.
Andrew Clark

Antworten:

104

Der wahrscheinlich einzige Weg, der geringfügig schneller ist als

d = [[] for x in xrange(n)]

ist

from itertools import repeat
d = [[] for i in repeat(None, n)]

Es muss nicht intbei jeder Iteration ein neues Objekt erstellen und ist auf meinem Computer etwa 15% schneller.

Bearbeiten : Mit NumPy können Sie die Python-Schleife mit vermeiden

d = numpy.empty((n, 0)).tolist()

Dies ist jedoch tatsächlich 2,5-mal langsamer als das Listenverständnis.

Sven Marnach
quelle
Wie wäre es mit map(lambda x:[], repeat(None,n))?
PaulMcG
3
@Paul: Das wäre aufgrund des Funktionsaufruf-Overheads des Lambda-Ausdrucks wieder viel langsamer.
Sven Marnach
Sollte dies nicht aktualisiert werden, da der Bereich in Python 3 anders ist?
Berühmte
@beruic Ich zitiere nur den Code aus der Frage im ersten Codeblock, daher ist es nicht wirklich sinnvoll, das zu ändern.
Sven Marnach
12

Das Listenverständnis wird tatsächlich effizienter implementiert als explizite Schleifen (siehe die disAusgabe zum Beispiel Funktionen ), und der mapWeg muss bei jeder Iteration ein ophaque-aufrufbares Objekt aufrufen, was einen erheblichen Overhead-Aufwand verursacht.

Unabhängig davon [[] for _dummy in xrange(n)]ist dies der richtige Weg und keiner der winzigen (wenn überhaupt vorhandenen) Geschwindigkeitsunterschiede zwischen verschiedenen anderen Wegen sollte von Bedeutung sein. Es sei denn, Sie verbringen die meiste Zeit damit - aber in diesem Fall sollten Sie stattdessen an Ihren Algorithmen arbeiten. Wie oft erstellen Sie diese Listen?


quelle
5
Bitte nein _als Variablenname! Ansonsten schöne Antwort :)
Sven Marnach
11
@Sven: Warum nicht? Es wird häufig für nicht verwendete Variablen verwendet (wenn es aufgerufen iwürde, würde ich jedenfalls suchen, wo es verwendet wird). Die einzige Gefahr wäre, dass es das _Halten des letzten Ergebnisses in der REPL beschattet ... und das ist nur in 2.x der Fall, wo das Listenverständnis leckt.
Nicht sehr oft, weshalb ich ein Listenverständnis verwendet habe. Ich dachte, es wäre interessant zu sehen, was die Leute zu sagen haben. Ich habe gesehen, wie die Dropbox bei PyCon mit der Verwendung von itertools.imap anstelle einer for-Schleife gesprochen hat, um einen MD5-Hash absurd oft zu aktualisieren, und seitdem bin ich ein bisschen besessen von C-Schleifen.
Munchybunch
12
Der wichtigste Grund, es nicht zu verwenden, ist, dass es die Leute verwirrt und sie denken lässt, dass es eine Art spezielle Syntax ist. Zusätzlich zum Konflikt mit _dem interaktiven Interpreter widerspricht er auch dem allgemeinen gettext-Alias. Wenn Sie klarstellen möchten, dass die Variable eine Dummy-Variable ist, rufen Sie sie dummynicht auf _.
Sven Marnach
12

Hier sind zwei Methoden, eine süß und einfach (und konzeptionell), die andere formeller und kann nach dem Lesen eines Datensatzes in einer Vielzahl von Situationen erweitert werden.

Methode 1: Konzeptionell

X2=[]
X1=[1,2,3]
X2.append(X1)
X3=[4,5,6]
X2.append(X3)
X2 thus has [[1,2,3],[4,5,6]] ie a list of lists. 

Methode 2: Formal und erweiterbar

Eine weitere elegante Möglichkeit, eine Liste als Liste von Listen mit unterschiedlichen Nummern zu speichern, die aus einer Datei gelesen werden. (Die Datei hier enthält den Datensatzzug.) Zug ist ein Datensatz mit beispielsweise 50 Zeilen und 20 Spalten. dh. Zug [0] gibt mir die 1. Zeile einer CSV-Datei, Zug [1] gibt mir die 2. Zeile und so weiter. Ich bin daran interessiert, den Datensatz mit 50 Zeilen als eine Liste zu trennen, mit Ausnahme der Spalte 0, die hier meine erläuterte Variable ist. Daher muss sie aus dem ursprünglichen Zugdatensatz entfernt und dann die Liste nach der Liste vergrößert werden, dh eine Liste einer Liste . Hier ist der Code, der das macht.

Beachten Sie, dass ich in der inneren Schleife von "1" lese, da ich nur an erklärenden Variablen interessiert bin. Und ich initialisiere X1 = [] in der anderen Schleife neu, sonst wird X2.append ([0: (len (train [0]) - 1)]) X1 immer wieder neu schreiben - außerdem speichereffizienter.

X2=[]
for j in range(0,len(train)):
    X1=[]
    for k in range(1,len(train[0])):
        txt2=train[j][k]
        X1.append(txt2)
    X2.append(X1[0:(len(train[0])-1)])
ekta
quelle
4

Verwenden Sie die folgende Syntax, um eine Liste und eine Liste von Listen zu erstellen

     x = [[] for i in range(10)]

Dadurch wird eine 1-d-Liste erstellt. Um diese zu initialisieren, geben Sie die Nummer in [[Nummer] ein und legen die Länge der Liste fest. Die Länge wird in den Bereich (Länge) gesetzt.

  • Verwenden Sie die folgende Syntax, um eine Liste von Listen zu erstellen.
    x = [[[0] for i in range(3)] for i in range(10)]

Dadurch wird die Liste der Listen mit der Dimension 10 * 3 und dem Wert 0 initialisiert

  • Zugriff auf / Bearbeitung von Elementen
    x[1][5]=value
waqas ahmed
quelle
2

Also habe ich einige Geschwindigkeitsvergleiche durchgeführt, um den schnellsten Weg zu finden. Listenverständnisse sind in der Tat sehr schnell. Die einzige Möglichkeit, näher zu kommen, besteht darin, zu vermeiden, dass der Bytecode während der Erstellung der Liste ausgeblendet wird. Mein erster Versuch war die folgende Methode, die im Prinzip schneller zu sein scheint:

l = [[]]
for _ in range(n): l.extend(map(list,l))

(erzeugt natürlich eine Liste mit der Länge 2 ** n) Diese Konstruktion ist nach Zeitangaben sowohl für kurze als auch für lange (eine Million) Listen doppelt so langsam wie das Listenverständnis.

Mein zweiter Versuch war, mit starmap den Listenkonstruktor für mich aufzurufen. Es gibt eine Konstruktion, die den Listenkonstruktor mit Höchstgeschwindigkeit auszuführen scheint, aber immer noch langsamer ist, aber nur um einen winzigen Betrag:

from itertools import starmap
l = list(starmap(list,[()]*(1<<n)))

Interessanterweise deutet die Ausführungszeit darauf hin, dass es der letzte Listenaufruf ist, der die Starmap-Lösung verlangsamt, da ihre Ausführungszeit fast genau der Geschwindigkeit von:

l = list([] for _ in range(1<<n))

Mein dritter Versuch kam, als mir klar wurde, dass list (()) auch eine Liste erzeugt, und ich versuchte es scheinbar einfach:

l = list(map(list, [()]*(1<<n)))

Dies war jedoch langsamer als der Starmap-Aufruf.

Fazit: Für die Speed ​​Maniacs: Verwenden Sie das Listenverständnis. Rufen Sie Funktionen nur auf, wenn Sie müssen. Verwenden Sie integrierte Funktionen.

Jurjen Bos
quelle