[英]Better way to swap elements in a list?
我有一堆看起来像这样的列表:
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
我想交换元素如下:
final_l = [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
列表的大小可能会有所不同,但它们总是包含偶数个元素。
我是Python的新手,我现在这样做:
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
final_l = []
for i in range(0, len(l)/2):
final_l.append(l[2*i+1])
final_l.append(l[2*i])
我知道这不是真正的Pythonic,并且想要使用更高效的东西。 也许列表理解?
无需复杂的逻辑,只需使用切片和步骤重新排列列表:
In [1]: l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In [2]: l[::2], l[1::2] = l[1::2], l[::2]
In [3]: l
Out[3]: [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
编辑解释
我相信大多数观众已经熟悉列表切片和多重赋值。 如果你不这样做,我会尽力解释发生了什么(希望我不要让它变得更糟)。
要理解列表切片, 这里已经有了一个很好的答案和列表切片表示法的解释。 简单的说:
a[start:end] # items start through end-1
a[start:] # items start through the rest of the array
a[:end] # items from the beginning through end-1
a[:] # a copy of the whole array
There is also the step value, which can be used with any of the above:
a[start:end:step] # start through not past end, by step
让我们看看OP的要求:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # list l
^ ^ ^ ^ ^ ^ ^ ^ ^ ^
0 1 2 3 4 5 6 7 8 9 # respective index of the elements
l[0] l[2] l[4] l[6] l[8] # first tier : start=0, step=2
l[1] l[3] l[5] l[7] l[9] # second tier: start=1, step=2
-----------------------------------------------------------------------
l[1] l[3] l[5] l[7] l[9]
l[0] l[2] l[4] l[6] l[8] # desired output
第一层将是: l[::2] = [1, 3, 5, 7, 9]
第二层将是: l[1::2] = [2, 4, 6, 8, 10]
由于我们想要重新分配first = second
& second = first
,我们可以使用多个赋值,并更新原始列表:
first , second = second , first
那是:
l[::2], l[1::2] = l[1::2], l[::2]
作为旁注,为了获得新列表但不改变原始l
,我们可以从l
分配新列表,并在上面执行,即:
n = l[:] # assign n as a copy of l (without [:], n still points to l)
n[::2], n[1::2] = n[1::2], n[::2]
希望我不要把你们这些额外的解释混淆。 如果有,请帮助更新我的并使其更好:-)
这里有一个列表理解,可以解决这个问题:
In [1]: l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In [2]: [l[i^1] for i in range(len(l))]
Out[2]: [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
理解它的关键是以下演示如何置换列表索引:
In [3]: [i^1 for i in range(10)]
Out[3]: [1, 0, 3, 2, 5, 4, 7, 6, 9, 8]
^
是独占或运算符。 所有i^1
确实是翻转的至少-显著位i
,有效地与1交换0,2与3等。
>>> from itertools import chain
>>>
>>> list(chain(*zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
或者,您可以使用itertools.chain.from_iterable()
来避免额外的解包:
>>> list(chain.from_iterable(zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Python 2.7:
('inp1 ->', 15.302665948867798) # NPE's answer
('inp2a ->', 10.626379013061523) # alecxe's answer with chain
('inp2b ->', 9.739919185638428) # alecxe's answer with chain.from_iterable
('inp3 ->', 2.6654279232025146) # Anzel's answer
Python 3.4:
inp1 -> 7.913498195000102
inp2a -> 9.680125927000518
inp2b -> 4.728151862000232
inp3 -> 3.1804273489997286
如果你对python 2和3之间的不同表现感到好奇,原因如下:
正如你所看到的,@ NPE的答案( inp1
)在python3.4中表现得更好,原因在于python3.X range()
是一个智能对象,并不像列表那样保留内存中该范围之间的所有项目。
在许多方面,
range()
返回的对象表现得好像它是一个列表,但事实上并非如此。 它是一个对象,当您迭代它时返回所需序列的连续项,但它并不真正使列表,从而节省空间。
这就是为什么在python 3中它切换范围对象时不会返回列表。
# python2.7
>>> range(10)[2:5]
[2, 3, 4]
# python 3.X
>>> range(10)[2:5]
range(2, 5)
第二个重大变化是第三种方法( inp3
)的性能增加。 正如您所看到的,它与最后一个解决方案之间的差异已减少到约2秒(从~7秒)。 原因是因为zip()
函数在python3.X中它返回一个迭代器,它根据需要生成项目。 并且因为chain.from_iterable()
需要再次遍历项目,所以在此之前完成它是完全多余的(在zip
2中这个zip
做什么)。
码:
from timeit import timeit
inp1 = """
[l[i^1] for i in range(len(l))]
"""
inp2a = """
list(chain(*zip(l[1::2], l[0::2])))
"""
inp2b = """
list(chain.from_iterable(zip(l[1::2], l[0::2])))
"""
inp3 = """
l[::2], l[1::2] = l[1::2], l[::2]
"""
lst = list(range(100000))
print('inp1 ->', timeit(stmt=inp1,
number=1000,
setup="l={}".format(lst)))
print('inp2a ->', timeit(stmt=inp2a,
number=1000,
setup="l={}; from itertools import chain".format(lst)))
print('inp2b ->', timeit(stmt=inp2b,
number=1000,
setup="l={}; from itertools import chain".format(lst)))
print('inp3 ->', timeit(stmt=inp3,
number=1000,
setup="l={}".format(lst)))
另一种方法是,创建嵌套列表,使用反转顺序的对,然后使用itertools.chain.from_iterable
将列表展平
>>> from itertools import chain
>>> l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(chain.from_iterable([[l[i+1],l[i]] for i in range(0,(len(l)-1),2)]))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
编辑:我刚刚将Kasramvd的基准测试应用到我的解决方案中,我发现这个解决方案比其他顶级答案慢,所以我不推荐它用于大型列表。 如果性能不重要,我仍然觉得这很可读。
使用chain
和list comprehension
的可能答案之一
>>> l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(chain([(l[2*i+1], l[2*i]) for i in range(0, len(l)/2)]))
[(2, 1), (4, 3), (6, 5), (8, 7), (10, 9)]
另一种简单重新分配和切片技术的方法
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for a in range(0,len(l),2):
l[a:a+2] = l[a-len(l)+1:a-1-len(l):-1]
print l
产量
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
为了好玩,如果我们在更一般的范围内将“swap”解释为“反向”,则itertools.chain.from_iterable
方法可用于更长的子序列。
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def chunk(list_, n):
return (list_[i:i+n] for i in range(0, len(list_), n))
list(chain.from_iterable(reversed(c) for c in chunk(l, 4)))
# [4, 3, 2, 1, 8, 7, 6, 5, 10, 9]
(另一种)替代方案:
final_l = list() # make an empty list
for i in range(len(l)): # for as many items there are in the original list
if i % 2 == 0: # if the item is even
final_l.append(l[i+1]) # make this item in the new list equal to the next in the original list
else: # else, so when the item is uneven
final_l.append(l[i-1]) # make this item in the new list equal to the previous in the original list
这假定原始列表具有偶数个项目。 如果没有,可以添加try-except :
final_l = list()
for i in range(len(l)):
if i % 2 == 0:
try: # try if we can add the next item
final_l.append(l[i+1])
except: # if we can't (because i+1 doesnt exist), add the current item
final_l.append(l[i])
else:
final_l.append(l[i-1])
一种使用Numpy的方法
import numpy as np
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
l = np.array(l)
final_l = list(np.flip(l.reshape(len(l)//2,2), 1).flatten())
我根本没有看到你的实施有什么问题。 但你也许可以做一个简单的交换。
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in range(0, len(l), 2):
old = l[i]
l[i] = l[i+1]
l[i+1] = old
编辑显然,Python有一个更好的方法来进行交换,这将使代码像这样
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in range(0, len(l), 2):
l[i], l[i+1] = l[i+1], l[i]
newList = [(x[2*i+1], x[2*i]) for i in range(0, len(x)/2)]
现在找到解压缩元组的方法。 我不会做你所有的功课。
这里是一个基于modulo
运算符的解决方案:
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even = []
uneven = []
for i,item in enumerate(l):
if i % 2 == 0:
even.append(item)
else:
uneven.append(item)
list(itertools.chain.from_iterable(zip(uneven, even)))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.