如何反转列表或向后循环列表?
- 2024-11-25 08:49:00
- admin 原创
- 194
问题描述:
如何在 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:
逆向方法总结
有三种不同的内置方法来反转列表。哪种方法最好取决于您是否需要:
就地反转现有列表(改变原始列表变量)
最好的解决方案是
object.reverse()
方法
创建反向列表的迭代器(因为您要将其提供给 for 循环、生成器等)
最好的解决方案是
reversed(object)
创建迭代器
创建列表的副本,但顺序相反(以保留原始列表)
最好的解决方案是使用步长为 -1 的切片:
object[::-1]
从速度角度来看,最好使用上述内置函数来反转列表。对于反转,与手动创建的循环或生成器相比,它们在短列表(10 个项目)上的速度要快 2 到 8 倍,在长列表上的速度要快 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()
函数。直接在列表对象上运行此函数,所有项目的顺序将被反转:
请注意,以下将反转给定的原始变量,即使它也会返回反转的列表。即,您可以使用此函数输出创建副本。通常,您不会为此创建函数,但计时脚本需要它。
我们测试了这两种方法的性能 - 首先只是就地反转列表(更改原始列表),然后复制列表并随后反转它,以查看与其他方法相比这是否是创建反转副本的最快方法。
def rev_in_place(mylist):
mylist.reverse()
return mylist
def rev_copy_reverse(mylist):
a = copy(mylist)
a.reverse()
return a
方法 2:使用切片反转列表obj[::-1]
内置索引切片方法允许您复制任何索引对象的一部分。
不影响原物体
它构建一个完整列表,而不是迭代器
通用语法是:<object>[first_index:last_index:step]
。要利用切片创建一个简单的反向列表,请使用:<list>[::-1]
。当将选项留空时,它会将它们设置为对象的第一个和最后一个元素的默认值(如果步长为负,则反转)。
索引允许使用负数,即从对象索引末尾向后计数(即 -2 是倒数第二个项目)。当步长为负数时,它将从最后一个项目开始,并按该数量向后索引。
def rev_slice(mylist):
a = mylist[::-1]
return a
reversed(obj)
方法 3:使用迭代器函数反转列表
有一个reversed(indexed_object)
函数:
这将创建一个反向索引迭代器,而不是列表。如果您将其输入到循环中以获得更好的大型列表性能,那么这是一个很好的选择。
这将创建一个副本,并且不会影响原始对象
使用原始迭代器进行测试,并从迭代器创建列表。
def reversed_iterator(mylist):
a = reversed(mylist)
return a
def reversed_with_list(mylist):
a = list(reversed(mylist))
return a
方法 4:使用自定义/手动索引的反向列表
正如时间所显示的,创建自己的索引方法不是一个好主意。除非您确实需要执行自定义操作,否则请使用内置方法。这仅仅意味着学习内置方法。
也就是说,列表规模较小时不会产生很大的损失,但当列表规模扩大时,损失就会变得非常大。我确信下面的代码可以进行优化,但它永远无法与内置方法相媲美,因为它们是直接用本机语言实现的。
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
每种方法的计时
以下是脚本的其余部分,用于对每种反转方法进行计时。它表明使用迭代器进行反转obj.reverse()
和创建reversed(obj)
迭代器始终是最快的,而使用切片是创建副本的最快方法。
这也证明,除非万不得已,否则不要试图独自创造一种方法来做这件事!
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 毫秒,使用切片方法需要 ~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 年 12 月将推出 Python 3.11 性能改进
我快速比较了 Python 3.7 和 3.11(刚才在同一台 PC 上),运行了 100 万次循环,看看 3.11 的性能改进会带来什么不同。Python 3.11 平均快 26.5%,手动和内置反转列表方法的速度大致相同。
解决方案 5:
我发现(与其他一些建议相反)这l.reverse()
是迄今为止在 Python 3 和 2 中反转长列表的最快方法。我有兴趣知道其他人是否可以复制这些时间。
l[::-1]
可能更慢,因为它在反转列表之前会先复制列表。list()
在迭代器周围添加调用reversed(l)
必定会增加一些开销。当然,如果您想要列表或迭代器的副本,请使用相应的方法,但如果您只想反转列表,那么这l.reverse()
似乎是最快的方法。
功能
def rev_list1(l):
return l[::-1]
def rev_list2(l):
return list(reversed(l))
def rev_list3(l):
l.reverse()
return l
列表
l = list(range(1000000))
Python 3.5 计时
timeit(lambda: rev_list1(l), number=1000)
# 6.48
timeit(lambda: rev_list2(l), number=1000)
# 7.13
timeit(lambda: rev_list3(l), number=1000)
# 0.44
Python 2.7 计时
timeit(lambda: rev_list1(l), number=1000)
# 6.76
timeit(lambda: rev_list2(l), number=1000)
# 9.18
timeit(lambda: rev_list3(l), number=1000)
# 0.46
解决方案 6:
for x in array[::-1]:
do stuff
解决方案 7:
array=[0,10,20,40]
for e in reversed(array):
print e
解决方案 8:
另一个解决方案是使用numpy.flip
import numpy as np
array = [0, 10, 20, 40]
list(np.flip(array))
[40, 20, 10, 0]
解决方案 9:
您还可以使用数组索引的按位补码反向遍历数组:
>>> array = [0, 10, 20, 40]
>>> [array[~i] for i, _ in enumerate(array)]
[40, 20, 10, 0]
无论你做什么,都不要这样做;)
解决方案 10:
如果您想将反向列表的元素存储在其他变量中,那么您可以使用revArray = array[::-1]
或revArray = list(reversed(array))
。
但第一个变体稍微快一些:
z = range(1000000)
startTimeTic = time.time()
y = z[::-1]
print("Time: %s s" % (time.time() - startTimeTic))
f = range(1000000)
startTimeTic = time.time()
g = list(reversed(f))
print("Time: %s s" % (time.time() - startTimeTic))
输出:
Time: 0.00489711761475 s
Time: 0.00609302520752 s
解决方案 11:
使用列表理解:
[array[n] for n in range(len(array)-1, -1, -1)]
解决方案 12:
def reverse(my_list):
L = len(my_list)
for i in range(L/2):
my_list[i], my_list[L-i - 1] = my_list[L-i-1], my_list[i]
return my_list
解决方案 13:
您始终可以将列表视为堆栈,只需从列表的后端弹出堆栈顶部的元素即可。这样,您就可以利用堆栈的先进后出特性。当然,您正在使用第一个数组。我确实喜欢这种方法,因为它非常直观,因为您会看到一个列表从后端被使用,而另一个列表从前端构建。
>>> l = [1,2,3,4,5,6]; nl=[]
>>> while l:
nl.append(l.pop())
>>> print nl
[6, 5, 4, 3, 2, 1]
解决方案 14:
您的要求最直接的翻译成 Python 是这样的for
语句:
for i in xrange(len(array) - 1, -1, -1):
print i, array[i]
这虽然有些神秘,但也许有用。
解决方案 15:
通过切换相反索引的引用进行就地反转:
>>> l = [1,2,3,4,5,6,7]
>>> for i in range(len(l)//2):
... l[i], l[-1-i] = l[-1-i], l[i]
...
>>> l
[7, 6, 5, 4, 3, 2, 1]
解决方案 16:
>>> L = [1, 2, 3, 4]
>>> L = [L[-i] for i in range(1, len(L) + 1)]
>>> L
[4, 3, 2, 1]
解决方案 17:
>>> l = [1, 2, 3, 4, 5]
>>> print(reduce(lambda acc, x: [x] + acc, l, []))
[5, 4, 3, 2, 1]
解决方案 18:
此类使用 Python 魔术方法和迭代器进行反转,并反转列表:
class Reverse(object):
""" Builds a reverse method using magic methods """
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
REV_INSTANCE = Reverse([0, 10, 20, 40])
iter(REV_INSTANCE)
rev_list = []
for i in REV_INSTANCE:
rev_list.append(i)
print(rev_list)
输出
[40, 20, 10, 0]