Python: najszybszy sposób na utworzenie listy n list

97

Zastanawiałem się więc, jak najlepiej utworzyć listę pustych list:

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

Ze względu na sposób, w jaki Python działa z listami w pamięci, to nie działa:

[[]]*n

To tworzy, [[],[],...]ale każdy element jest tą samą listą:

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

Coś w rodzaju rozumienia listy działa:

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

Ale to używa maszyny wirtualnej Python do zapętlenia. Czy istnieje sposób na użycie implikowanej pętli (wykorzystując ją napisaną w C)?

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

To jest faktycznie wolniejsze. :(

munchybunch
źródło
1
Zdziwiłbym się, gdyby było coś znacznie szybszego niż d = [[] for x in xrange(0,n)]. Musisz albo wykonać pętlę jawnie w Pythonie, albo wielokrotnie wywoływać funkcję / lambdę Pythona (co powinno być wolniejsze). Ale wciąż mam nadzieję, że ktoś opublikuje coś, co pokazuje, że się mylę :).
MAK
1
timeitCzego się nauczyłeś , mierząc te wartości ?
S.Lott
Właśnie potwierdziłem, że map(lambda x: [], xrange(n))jest to wolniejsze niż rozumienie listy.
Andrew Clark

Odpowiedzi:

105

Prawdopodobnie jedyny sposób, który jest nieznacznie szybszy niż

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

jest

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

Nie musi tworzyć nowego intobiektu w każdej iteracji i jest około 15% szybszy na moim komputerze.

Edycja : używając NumPy, możesz uniknąć pętli Pythona używając

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

ale w rzeczywistości jest to 2,5 razy wolniejsze niż rozumienie listy.

Sven Marnach
źródło
A co powiesz map(lambda x:[], repeat(None,n))?
PaulMcG
3
@Paul: To byłoby o wiele wolniejsze ze względu na narzut wywołania funkcji wyrażenia lambda.
Sven Marnach
Czy nie należy tego aktualizować teraz, gdy zakres jest inny w Pythonie 3?
beruic
@beruic Po prostu cytuję kod z pytania w pierwszym bloku kodu, więc nie ma sensu tego zmieniać.
Sven Marnach
12

Listy składane są faktycznie implementowane wydajniej niż jawne zapętlanie (zobacz dane diswyjściowe, na przykład funkcje ), a mapsposób musi wywoływać nieprzejrzysty wywoływalny obiekt w każdej iteracji, co wiąże się ze znacznym narzutem.

Niezależnie od tego, [[] for _dummy in xrange(n)]jest to właściwy sposób i żadna z drobnych (jeśli w ogóle istnieją) różnic prędkości między różnymi innymi sposobami nie powinna mieć znaczenia. O ile oczywiście nie spędzasz na tym większości czasu - ale w takim przypadku powinieneś zamiast tego popracować nad algorytmami. Jak często tworzysz te listy?


źródło
5
Proszę, nie _jako nazwę zmiennej! W przeciwnym razie miła odpowiedź :)
Sven Marnach
11
@Sven: Dlaczego nie? Jest powszechnie używany dla nieużywanych zmiennych (gdyby został wywołany i, szukałbym , gdzie jest używany). Jedyną pułapką byłoby to, że przesłaniałby _ostatni wynik w REPL ... i tak jest tylko w przypadku 2.x, w którym wyciekają wyrażenia listy.
Niezbyt często, dlatego zdecydowałem się na rozumienie listy. Pomyślałem, że byłoby interesujące zobaczyć, co ludzie mają do powiedzenia. Widziałem rozmowę Dropbox na PyCon z użyciem itertools.imap zamiast pętli for do aktualizacji skrótu md5 absurdalnie wiele razy i od tego czasu mam trochę obsesji na punkcie pętli C.
munchybunch
12
Najważniejszym powodem, dla którego go nie używamy, jest to, że ma tendencję do dezorientowania ludzi, sprawiając, że myślą, że jest to jakaś specjalna składnia. Oprócz konfliktu z _w interaktywnym interpretatorze, jest również w konflikcie ze wspólnym aliasem gettext. Jeśli chcesz wyjaśnić, że zmienna jest zmienną fikcyjną, nazwij ją dummy, a nie _.
Sven Marnach
12

Oto dwie metody, jedna słodka i prosta (i koncepcyjna), druga bardziej formalna i może być rozszerzona w różnych sytuacjach po przeczytaniu zbioru danych.

Metoda 1: koncepcyjne

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. 

Metoda 2: Formalna i rozszerzalna

Kolejny elegancki sposób przechowywania listy jako listy list różnych numerów - które odczytuje z pliku. (W tym pliku znajduje się pociąg zestawu danych) Train to zestaw danych zawierający powiedzmy 50 wierszy i 20 kolumn. to znaczy. Train [0] daje mi pierwszy wiersz pliku csv, train [1] drugi wiersz i tak dalej. Interesuje mnie oddzielenie zbioru danych z 50 wierszami jako jedną listę, z wyjątkiem kolumny 0, która jest tutaj moją wyjaśnioną zmienną, więc należy ją usunąć z oryginalnego zestawu danych pociągu, a następnie przeskalować listę po liście w górę - tj. Lista listy . Oto kod, który to robi.

Zauważ, że czytam od „1” w pętli wewnętrznej, ponieważ interesują mnie tylko zmienne objaśniające. I ponownie inicjalizuję X1 = [] w drugiej pętli, w przeciwnym razie X2.append ([0: (len (train [0]) - 1)]) przepisze X1 w kółko - poza tym bardziej wydajna pamięć.

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
źródło
4

Aby utworzyć listę i listę list, użyj poniższej składni

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

to utworzy listę 1-d i aby ją zainicjalizować, umieść numer w [[liczba] i ustaw długość listy wstawionej długości w przedziale (długość)

  • Aby utworzyć listę list, użyj poniższej składni.
    x = [[[0] for i in range(3)] for i in range(10)]

spowoduje to zainicjowanie listy list o wymiarze 10 * 3 i wartości 0

  • Aby uzyskać dostęp do elementu / manipulować nim
    x[1][5]=value
waqas ahmed
źródło
2

Więc zrobiłem kilka porównań prędkości, aby uzyskać najszybszą drogę. Rozumienie listy jest rzeczywiście bardzo szybkie. Jedynym sposobem na zbliżenie się jest uniknięcie wykonania kodu bajtowego podczas tworzenia listy. Moja pierwsza próba była następująca, która wydawałaby się w zasadzie szybsza:

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

(oczywiście tworzy listę o długości 2 ** n) Konstrukcja ta jest dwa razy wolniejsza niż rozumienie list według timeit, zarówno dla krótkich, jak i długich (milion) list.

Moją drugą próbą było użycie starmap do wywołania konstruktora listy za mnie.Istnieje jedna konstrukcja, która wydaje się uruchamiać konstruktor listy z maksymalną prędkością, ale nadal jest wolniejsza, ale tylko o niewielką wartość:

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

Co ciekawe, czas wykonania sugeruje, że to ostatnie wywołanie listy spowalnia rozwiązanie Starmap, ponieważ jego czas wykonania jest prawie dokładnie równy szybkości:

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

Moja trzecia próba nastąpiła, gdy zdałem sobie sprawę, że lista (()) również tworzy listę, więc wypróbowałem pozornie proste:

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

ale to było wolniejsze niż sygnał gwiezdny.

Wniosek: dla maniaków prędkości: używaj rozumienia listy. Wywołaj funkcje tylko, jeśli musisz. Użyj wbudowanych.

Jurjen Bos
źródło