Найкращий спосіб створити "зворотний" список у Python?


91

У Python, який найкращий спосіб створити новий список, елементи якого збігаються з елементами іншого списку, але в зворотному порядку? (Я не хочу змінювати наявний список на місці.)

Ось одне з рішень, яке мені спало на думку:

new_list = list(reversed(old_list))

Також можна продублювати, old_listа потім повернути дублікат на місце:

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

Чи є кращий варіант, який я пропустив? Якщо ні, чи є вагома причина (наприклад, ефективність) використовувати один із наведених підходів над іншим?

Відповіді:


207
newlist = oldlist[::-1]

[::-1]Нарізка (що моя дружина Анна любить називати «марсіанську смайлик» ;-) означає: зріз всієї послідовності, з кроком -1, тобто в зворотному. Це працює для всіх послідовностей.

Зверніть увагу, що це ( та альтернативи, про які ви згадали) еквівалентно "неглибокій копії", тобто: якщо елементи можна змінювати, і ви викликаєте на них мутаторів, мутації елементів, що містяться в оригінальному списку, також містяться в елементах у перевернутий список, і навпаки. Якщо вам потрібно цього уникнути, єдиним хорошим варіантом є a copy.deepcopy(хоча це завжди потенційно дорога операція), за яким у цьому випадку йде a .reverse.


О мій! Це є елегантним. Ще кілька днів тому я не усвідомлював, що можна включати "крок" під час нарізки; зараз мені цікаво, як я коли-небудь пройшов без цього! Дякую, Алекс. :)
davidchambers

1
Дякую також за те, що згадали, що це дає дрібну копію. Однак це все, що мені потрібно, тому я збираюся додати марсіанський смайлик до свого коду.
davidchambers

13
Це дійсно здається набагато більше магії і багато менш читабельним , ніж вихідне пропозицію, list(reversed(oldlist)). За винятком незначних мікро-оптимізації, чи є підстава вважати за краще , [::-1]щоб reversed()?
Брайан Кемпбелл

@BrianCampbell: Що в цьому "чарівного"? Якщо ви взагалі розумієте нарізання, це має цілковитий сенс. Якщо ви не розумієте нарізки ... ну, вам дійсно слід навчитися цьому досить рано у вашій кар'єрі Python. Звичайно, reversedмає величезну перевагу, коли вам не потрібен список, оскільки він не витрачає пам’ять і не заздалегідь створює його. Але коли ви робите потрібен список, використовуючи [::-1]замість list(reversed())аналогічно з використанням [listcomp]замість list(genexpr).
abarnert

11
@abarnert У коді, з яким я працюю, я так рідко бачу аргумент третього зрізу, якщо я бачу його використання, мені доводиться шукати, що це означає. Як тільки я це роблю, все ще не очевидно, що значення початку та кінця за замовчуванням міняються місцями, коли крок від’ємний. Швидким поглядом, не шукаючи значення третього аргументу, я міг би здогадатися, що це [::-1]означає видалення останнього елемента списку, а не його зміни. reversed(list)вказує, що саме робить; він викладає свій намір у значенні: "Явний краще, ніж неявний", "Читаність враховується" та "Розріджений краще, ніж щільний".
Брайан Кемпбелл,

56

А тепер давайте timeit. Підказка: Алекс [::-1]найшвидший :)

$ 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

Оновлення: Доданий метод comp comp, запропонований inspectorG4dget. Я нехай результати говорять самі за себе.


8
Тільки примітка - це точно для створення зворотного списку копій, але зворотний все ще ефективніший для ітерації, ніж тоді[::-1]
Тадгг Макдональд-Дженсен

7

Коригування

Варто надати базовий орієнтир / коригування для обчислення часу за sdolan, який показує ефективність "зворотного" без часто непотрібного list()перетворення. Ця list()операція додає додаткові 26 usecs до середовища виконання і потрібна лише в тому випадку, якщо ітератор неприйнятний.

Результати:

reversed(lst) -- 11.2 usecs

list(reversed(lst)) -- 37.1 usecs

lst[::-1] -- 23.6 usecs

Розрахунки:

# 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

Висновки:

Висновок цих тестів reversed()швидший, ніж фрагмент, [::-1]на 12,4 usecs


15
reversed () повертає об'єкт-ітератор, який оцінюється ліниво, тому я думаю, що це нечесне порівняння із позначенням нарізки [:: - 1] загалом.
райдужно

1
Навіть у тому випадку, коли ітератор можна використовувати безпосередньо, наприклад ''.join(reversed(['1','2','3'])), метод зрізу все одно> 30% швидший.
dansalmo

1
Не дивно, чому ви отримали однаковий результат із перших 2 тестів: вони ідентичні!
MestreLion

Мої результати більше нагадують це. ol [:: - 1] займає приблизно вдвічі більше часу, ніж список (зворотний (ol)). Метод ol [:: - 1] займає менше символів для запису. Однак list (reversed (ol)), мабуть, легше читати для початківців програмістів на python, і він швидший на моїй машині.
dhj
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.