Najlepszy sposób na utworzenie „odwróconej” listy w Pythonie?

92

W Pythonie, jaki jest najlepszy sposób na utworzenie nowej listy, której elementy są takie same, jak elementy innej listy, ale w odwrotnej kolejności? (Nie chcę modyfikować istniejącej listy w miejscu).

Oto jedno rozwiązanie, które przyszło mi do głowy:

new_list = list(reversed(old_list))

Możliwe jest również skopiowanie, old_lista następnie odwrócenie kopii w miejscu:

new_list = list(old_list) # or `new_list = old_list[:]`
new_list.reverse()

Czy jest lepsza opcja, którą przeoczyłem? Jeśli nie, czy istnieje ważny powód (taki jak efektywność), aby zastosować jedno z powyższych podejść zamiast drugiego?

davidchambers
źródło

Odpowiedzi:

208
newlist = oldlist[::-1]

[::-1]Krojenie (co moja żona Anna lubi nazywać „marsjański buźkę” ;-) środki: Kawałki całą sekwencję, z krokiem -1, czyli w odwrotnej kolejności. Działa dla wszystkich sekwencji.

Zauważ, że to ( i alternatywy, o których wspomniałeś) jest równoważne z "płytką kopią", tj .: jeśli elementy są zmienne i wywołujesz na nich mutatory, mutacje w elementach utrzymywanych na pierwotnej liście znajdują się również w elementach w odwrócona lista i odwrotnie. Jeśli chcesz tego uniknąć, jedyną dobrą opcją jest a copy.deepcopy(choć zawsze jest to potencjalnie kosztowna operacja), po którym w tym przypadku następuje a .reverse.

Alex Martelli
źródło
O mój! To jest eleganckie. Jeszcze kilka dni temu nie zdawałem sobie sprawy, że podczas krojenia można uwzględnić „krok”; teraz zastanawiam się, jak sobie bez niego radziłem! Dzięki, Alex. :)
davidchambers
1
Dziękuję też za wspomnienie, że w ten sposób powstaje płytka kopia. To wszystko, czego potrzebuję, więc zamierzam dodać marsjański emotikon do mojego kodu.
davidchambers
13
To naprawdę wydaje się o wiele więcej magii i dużo mniej czytelny niż oryginalny sugestii list(reversed(oldlist)). Poza drobną mikro-optymalizacją, czy jest jakiś powód, aby to [::-1]zrobić reversed()?
Brian Campbell
@BrianCampbell: Co w tym „magii”? Jeśli w ogóle rozumiesz krojenie, ma to sens. Jeśli nie rozumiesz krojenia… cóż, naprawdę powinieneś nauczyć się tego dość wcześnie w swojej karierze w Pythonie. Oczywiście reversedma ogromną zaletę, gdy lista nie jest potrzebna, ponieważ nie marnuje pamięci ani czasu na jej tworzenie. Ale kiedy zrobić potrzebujemy listy, używając [::-1]zamiast list(reversed())jest analogiczna do stosując [listcomp]zamiast list(genexpr).
abarnert
11
@abarnert W kodzie, z którym pracuję, tak rzadko widzę argument trzeciego wycinka, jeśli widzę jego zastosowanie, muszę sprawdzić, co to znaczy. Gdy to zrobię, nadal nie jest oczywiste, że domyślne wartości początkowe i końcowe są zamieniane, gdy krok jest ujemny. Na pierwszy rzut oka, bez szukania znaczenia trzeciego argumentu, mógłbym się domyślić, że [::-1]oznacza to raczej usunięcie ostatniego elementu listy niż jego odwrócenie. reversed(list)dokładnie określa, co robi; określa swój zamiar w sensie „Wyraźne jest lepsze niż ukryte”, „Czytelność się liczy” i „Rzadkie jest lepsze niż gęste”.
Brian Campbell,
56

A teraz timeit. Podpowiedź: Alex [::-1]jest najszybszy :)

$ p -m timeit "ol = [1, 2, 3]; nl = list(reversed(ol))"
100000 loops, best of 3: 2.34 usec per loop

$ p -m timeit "ol = [1, 2, 3]; nl = list(ol); nl.reverse();"
1000000 loops, best of 3: 0.686 usec per loop

$ p -m timeit "ol = [1, 2, 3]; nl = ol[::-1];"
1000000 loops, best of 3: 0.569 usec per loop

$ p -m timeit "ol = [1, 2, 3]; nl = [i for i in reversed(ol)];"
1000000 loops, best of 3: 1.48 usec per loop


$ p -m timeit "ol = [1, 2, 3]*1000; nl = list(reversed(ol))"
10000 loops, best of 3: 44.7 usec per loop

$ p -m timeit "ol = [1, 2, 3]*1000; nl = list(ol); nl.reverse();"
10000 loops, best of 3: 27.2 usec per loop

$ p -m timeit "ol = [1, 2, 3]*1000; nl = ol[::-1];"
10000 loops, best of 3: 24.3 usec per loop

$ p -m timeit "ol = [1, 2, 3]*1000; nl = [i for i in reversed(ol)];"
10000 loops, best of 3: 155 usec per loop

Aktualizacja: Dodano metodę kompilacji listy sugerowaną przez inspectorG4dget. Niech wyniki mówią same za siebie.

Sam Dolan
źródło
8
Tylko uwaga - jest to poprawne w przypadku tworzenia listy odwróconych kopii, ale odwrócenie jest nadal bardziej wydajne dla iteracji niż[::-1]
Tadhg McDonald-Jensen
7

Korekty

Warto podać bazowy punkt odniesienia / korektę dla obliczeń czasu trwania przez sdolan, które pokazują wyniki „odwrócone” bez często niepotrzebnych list()konwersji. Ta list()operacja dodaje dodatkowe 26 usek do środowiska wykonawczego i jest potrzebna tylko w przypadku, gdy iterator jest niedopuszczalny.

Wyniki:

reversed(lst) -- 11.2 usecs

list(reversed(lst)) -- 37.1 usecs

lst[::-1] -- 23.6 usecs

Obliczenia:

# I ran this set of 100000 and came up with 11.2, twice:
python -m timeit "ol = [1, 2, 3]*1000; nl = reversed(ol)"
100000 loops, best of 3: 11.2 usec per loop

# This shows the overhead of list()
python -m timeit "ol = [1, 2, 3]*1000; nl = list(reversed(ol))"
10000 loops, best of 3: 37.1 usec per loop

# This is the result for reverse via -1 step slices
python -m timeit "ol = [1, 2, 3]*1000;nl = ol[::-1]"
10000 loops, best of 3: 23.6 usec per loop

Wnioski:

Zakończenie tych testów reversed()jest szybsze niż wycinek [::-1]o 12,4 usek

mekarpeles
źródło
15
reverse () zwraca obiekt iteratora, który jest oceniany z opóźnieniem, więc myślę, że nie jest to uczciwe porównanie z notacją wycinania [:: - 1] w ogóle.
opalizujący
1
Nawet w przypadku, gdy iterator może być używany bezpośrednio, na przykład ''.join(reversed(['1','2','3']))metoda wycinania jest nadal> 30% szybsza.
dansalmo
1
Nic dziwnego, że w pierwszych dwóch testach otrzymałeś taki sam wynik: są identyczne!
MestreLion
Moje wyniki bardziej przypominają to. ol [:: - 1] zajmuje około dwa razy dłużej niż lista (odwrócone (ol)). Zapisanie metody ol [:: - 1] wymaga mniej znaków. Jednak lista (odwrócona (ol)) jest prawdopodobnie bardziej czytelna dla początkujących programistów Pythona i jest szybsza na moim komputerze.
dhj