Как мне перевернуть список или выполнить цикл над ним в обратном направлении?
Как мне выполнить итерацию по списку в обратном порядке в Python?
Смотрите также: Как я могу получить обратную копию списка (избегайте отдельного оператора при объединении метода в цепочку после .reverse)?
Переведено автоматически
Ответ 1
Чтобы получить новый перевернутый список, примените reversed
функцию и соберите элементы в list
:
>>> xs = [0, 10, 20, 40]
>>> list(reversed(xs))
[40, 20, 10, 0]
Для выполнения итерации в обратном направлении по списку:
>>> xs = [0, 10, 20, 40]
>>> for x in reversed(xs):
... print(x)
40
20
10
0
Ответ 2
>>> xs = [0, 10, 20, 40]
>>> xs[::-1]
[40, 20, 10, 0]
Синтаксис расширенного фрагмента объясняется здесь . Смотрите также документацию.
Ответ 3
Используйте list.reverse
для переворота списка на месте:
>>> xs = [0, 10, 20, 40]
>>> xs.reverse()
>>> xs
[40, 20, 10, 0]
Используйте фрагменты для создания нового списка с элементами в обратном порядке:
>>> xs[::-1]
[40, 20, 10, 0]
Ответ 4
Краткое описание обратных методов
Есть три разных встроенных способа перевернуть список. Какой метод лучше всего использовать, зависит от того, нужно ли вам:
- Перевернуть существующий список на месте (изменив исходную переменную list)
- Лучшее решение - это
object.reverse()
метод
- Лучшее решение - это
- Создайте итератор перевернутого списка (потому что вы собираетесь передать его в цикл for, генератор и т.д.)
- Лучшее решение - это
reversed(object)
который создает итератор
- Лучшее решение - это
- Создайте копию списка, только в обратном порядке (чтобы сохранить исходный список)
- Лучшее решение - использовать фрагменты с размером шага -1:
object[::-1]
- Лучшее решение - использовать фрагменты с размером шага -1:
С точки зрения скорости лучше всего использовать вышеупомянутые встроенные функции для обратного преобразования списка. Что касается реверсирования, то они в 2-8 раз быстрее для коротких списков (10 элементов) и более чем в ~ 300 раз быстрее для длинных списков по сравнению с созданным вручную циклом или генератором. Это имеет смысл - они написаны на родном языке (т. Е. C), их создают эксперты, проводят тщательную проверку и оптимизацию. Они также менее подвержены дефектам и с большей вероятностью справятся с крайними и угловыми случаями.
Тестовый скрипт
Соедините все фрагменты кода в этом ответе вместе, чтобы создать скрипт, который будет запускать различные способы обращения списка, описанные ниже. Время выполнения каждого метода будет рассчитано 100 000 раз. Результаты показаны в последнем разделе для списков длиной 2, 10 и 1000 элементов.
from timeit import timeit
from copy import copy
def time_str_ms(t):
return '{0:8.2f} ms'.format(t * 1000)
Метод 1: Перевернуть на место с помощью obj.reverse()
Если цель состоит в том, чтобы просто изменить порядок элементов в существующем списке, без зацикливания на них или получения копии для работы, используйте функцию <list>.reverse()
. Запустите это непосредственно над объектом list, и порядок всех элементов будет изменен на обратный:
Обратите внимание, что следующее изменит исходную переменную, которая задана, даже если она также вернет обратный список обратно. т. е. вы можете создать копию, используя вывод этой функции. Обычно вы не стали бы создавать функцию для этого, но сценарий синхронизации требует этого.
Мы тестируем производительность этих двух способов - сначала просто переворачиваем список на месте (изменяет исходный список), а затем копируем список и переворачиваем его позже, чтобы увидеть, является ли это самым быстрым способом создания обратной копии по сравнению с другими методами.
def rev_in_place(mylist):
mylist.reverse()
return mylist
def rev_copy_reverse(mylist):
a = copy(mylist)
a.reverse()
return a
Method 2: Reverse a list using slices obj[::-1]
The built-in index slicing method allows you to make a copy of part of any indexed object.
- It does not affect the original object
- It builds a full list, not an iterator
The generic syntax is: <object>[first_index:last_index:step]
. To exploit slicing to create a simple reversed list, use: <list>[::-1]
. When leaving an option empty, it sets them to defaults of the first and last element of the object (reversed if the step size is negative).
Indexing allows one to use negative numbers, which count from the end of the object's index backwards (i.e. -2 is the second to last item). When the step size is negative, it will start with the last item and index backward by that amount.
def rev_slice(mylist):
a = mylist[::-1]
return a
Method 3: Reverse a list with the reversed(obj)
iterator function
There is a reversed(indexed_object)
function:
- This creates a reverse index iterator, not a list. Great if you are feeding it to a loop for better performance on large lists
- This creates a copy and does not affect the original object
Test with both a raw iterator, and creating a list from the iterator.
def reversed_iterator(mylist):
a = reversed(mylist)
return a
def reversed_with_list(mylist):
a = list(reversed(mylist))
return a
Method 4: Reverse list with Custom/Manual indexing
As the timing shows, creating your own methods of indexing is a bad idea. Use the built-in methods unless you really do need to do something custom. This simply means learning the built-in methods.
That said, there is not a huge penalty with smaller list sizes, but when you scale up the penalty becomes tremendous. The code below could be optimized, I'm sure, but it can't ever match the built-in methods as they are directly implemented in a native language.
def rev_manual_pos_gen(mylist):
max_index = len(mylist) - 1
return [ mylist[max_index - index] for index in range(len(mylist)) ]
def rev_manual_neg_gen(mylist):
## index is 0 to 9, but we need -1 to -10
return [ mylist[-index-1] for index in range(len(mylist)) ]
def rev_manual_index_loop(mylist):
a = []
reverse_index = len(mylist) - 1
for index in range(len(mylist)):
a.append(mylist[reverse_index - index])
return a
def rev_manual_loop(mylist):
a = []
reverse_index = len(mylist)
for index, _ in enumerate(mylist):
reverse_index -= 1
a.append(mylist[reverse_index])
return a
Timing each method
Following is the rest of the script to time each method of reversing. It shows reversing in place with obj.reverse()
and creating the reversed(obj)
iterator are always the fastest, while using slices is the fastest way to create a copy.
It also proves not to try to create a way of doing it on your own unless you have to!
loops_to_test = 100000
number_of_items = 10
list_to_reverse = list(range(number_of_items))
if number_of_items < 15:
print("a: {}".format(list_to_reverse))
print('Loops: {:,}'.format(loops_to_test))
# List of the functions we want to test with the timer, in print order
fcns = [rev_in_place, reversed_iterator, rev_slice, rev_copy_reverse,
reversed_with_list, rev_manual_pos_gen, rev_manual_neg_gen,
rev_manual_index_loop, rev_manual_loop]
max_name_string = max([ len(fcn.__name__) for fcn in fcns ])
for fcn in fcns:
a = copy(list_to_reverse) # copy to start fresh each loop
out_str = ' | out = {}'.format(fcn(a)) if number_of_items < 15 else ''
# Time in ms for the given # of loops on this fcn
time_str = time_str_ms(timeit(lambda: fcn(a), number=loops_to_test))
# Get the output string for this function
fcn_str = '{}(a):'.format(fcn.__name__)
# Add the correct string length to accommodate the maximum fcn name
format_str = '{{fx:{}s}} {{time}}{{rev}}'.format(max_name_string + 4)
print(format_str.format(fx=fcn_str, time=time_str, rev=out_str))
Результаты синхронизации
ПРИМЕЧАНИЕ: Приведенные ниже результаты выполняются на Python 3.7.9
Результаты показывают, что масштабирование лучше всего работает со встроенными методами, которые лучше всего подходят для определенного типа реверсирования. Другими словами, по мере увеличения количества элементов объекта встроенные методы еще больше опережают другие методы.
Встроенный метод, который напрямую достигает того, что вам нужно, работает лучше, чем объединение объектов воедино. т. Е. Нарезка лучше всего подходит, если вам нужна копия перевернутого списка - это быстрее, чем создание дубликата списка из list(reversed(obj))
функции, и быстрее, чем создание копии списка, а затем выполнение на месте obj.reverse()
, но никогда не более чем в два раза быстрее. Между тем, пользовательские методы могут занимать на порядки больше времени при работе с большими списками.
Для масштабирования списка из 1000 элементов reversed(<list>)
вызов функции занимает ~ 30 мс для настройки итератора, реверсирование на месте занимает всего ~ 55 мс, использование метода slice занимает ~ 210 мс для создания копии полного перевернутого списка, но самый быстрый ручной метод, который я использовал, занял ~ 8400 мс.
С 2 элементами в списке:
a: [0, 1]
Loops: 100,000
rev_in_place(a): 24.70 ms | out = [1, 0]
reversed_iterator(a): 30.48 ms | out = <list_reverseiterator object at 0x0000020242580408>
rev_slice(a): 31.65 ms | out = [1, 0]
rev_copy_reverse(a): 63.42 ms | out = [1, 0]
reversed_with_list(a): 48.65 ms | out = [1, 0]
rev_manual_pos_gen(a): 98.94 ms | out = [1, 0]
rev_manual_neg_gen(a): 88.11 ms | out = [1, 0]
rev_manual_index_loop(a): 87.23 ms | out = [1, 0]
rev_manual_loop(a): 79.24 ms | out = [1, 0]
С 10 элементами в списке:
rev_in_place(a): 23.39 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
reversed_iterator(a): 30.23 ms | out = <list_reverseiterator object at 0x00000290A3CB0388>
rev_slice(a): 36.01 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
rev_copy_reverse(a): 64.67 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
reversed_with_list(a): 50.77 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
rev_manual_pos_gen(a): 162.83 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
rev_manual_neg_gen(a): 167.43 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
rev_manual_index_loop(a): 152.04 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
rev_manual_loop(a): 183.01 ms | out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
И с 1000 элементами в списке:
rev_in_place(a): 56.37 ms
reversed_iterator(a): 30.47 ms
rev_slice(a): 211.42 ms
rev_copy_reverse(a): 295.74 ms
reversed_with_list(a): 418.45 ms
rev_manual_pos_gen(a): 8410.01 ms
rev_manual_neg_gen(a): 11054.84 ms
rev_manual_index_loop(a): 10543.11 ms
rev_manual_loop(a): 15472.66 ms
Примечание: декабрь 2023 г. с улучшениями производительности Python 3.11
Я провел быстрое сравнение Python 3.7 с 3.11 (только что на том же ПК) с 1 миллионом циклов, чтобы увидеть, какую разницу внесут улучшения производительности в версии 3.11. Python 3.11 был в среднем на 26,5% быстрее, примерно такой же для ручных методов, как и для встроенных методов обращения списков.