Better way to swap elements in a list?
我有一大堆列表,看起来像这样:
1 | l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
我想交换如下元素:
1 | final_l = [2, 1, 4, 3, 6, 5, 8, 7, 10, 9] |
列表的大小可能会有所不同,但它们始终包含偶数个元素。
我对python比较陌生,目前正在这样做:
1 2 3 4 5 | 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]) |
我知道这不是真正的Python,我想用更有效的东西。也许是清单理解?
不需要复杂的逻辑,只需使用切片和步骤重新排列列表:
1 2 3 4 5 6 | 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] |
TLDR;
带解释的编辑
我相信大多数观众已经熟悉了列表分割和多重分配。如果你不这样做,我会尽我最大的努力解释发生了什么(希望我不要让事情变得更糟)。
为了理解列表切片,这里已经有了一个很好的列表切片符号的答案和解释。简单地说:
1 2 3 4 5 6 7 8 | 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 | [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 |
第一层是:
由于我们想重新分配
1 | first , second = second , first |
即:
1 | l[::2], l[1::2] = l[1::2], l[::2] |
作为补充说明,为了得到一个新的列表而不改变原来的
1 2 | 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] |
希望我不会把你们中的任何人与这个附加的解释混淆。如果有,请帮助更新我的并使其更好:-)
以下是一个单一的清单理解,它可以实现以下功能:
1 2 3 4 | 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] |
理解这一点的关键在于以下演示如何排列列表索引:
1 2 | In [3]: [i^1 for i in range(10)] Out[3]: [1, 0, 3, 2, 5, 4, 7, 6, 9, 8] |
您可以使用成对迭代和链接来扁平列表:
1 2 3 4 | >>> from itertools import chain >>> >>> list(chain(*zip(l[1::2], l[0::2]))) [2, 1, 4, 3, 6, 5, 8, 7, 10, 9] |
或者,您可以使用
1 2 | >>> list(chain.from_iterable(zip(l[1::2], l[0::2]))) [2, 1, 4, 3, 6, 5, 8, 7, 10, 9] |
最佳答案之间的基准:
Python 2.7:
1 2 3 4 | ('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:
1 2 3 4 | inp1 -> 7.913498195000102 inp2a -> 9.680125927000518 inp2b -> 4.728151862000232 inp3 -> 3.1804273489997286 |
如果您对python 2和3的不同性能感到好奇,那么原因如下:
正如你所看到的,@npe的答案(
In many ways the object returned by
range() behaves as if it is a list, but in fact it isn’t. It is an object which returns the successive items of the desired sequence when you iterate over it, but it doesn’t really make the list, thus saving space.
这就是为什么在python 3中,在分割range对象时它不返回列表的原因。
1 2 3 4 5 6 | # python2.7 >>> range(10)[2:5] [2, 3, 4] # python 3.X >>> range(10)[2:5] range(2, 5) |
第二个显著变化是第三种方法的性能增强(
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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))) |
另一种方法是创建嵌套列表,让成对的列表颠倒顺序,然后用
1 2 3 4 | >>> 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的基准测试应用到我的解决方案中,我发现这个解决方案比其他顶级答案慢,所以我不推荐它出现在大列表中。不过,如果性能不是关键的,我仍然觉得这很可读。
使用
1 2 3 | >>> 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)] |
另一种选择:
1 2 3 4 5 6 | 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 |
这假设原始列表的项目数为偶数。如果没有,可以添加一个尝试例外:
1 2 3 4 5 6 7 8 9 | 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]) |
有趣的是,如果我们在更一般的范围内将"swap"解释为"reverse",那么可以将
1 2 3 4 5 6 7 | 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] |
另一种简单的重新分配和切片技术
1 2 3 4 | 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 |
输出
1 | [2, 1, 4, 3, 6, 5, 8, 7, 10, 9] |
使用numpy的方法
1 2 3 4 5 | 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()) |
我认为您的实现没有任何问题。但是你可以做一个简单的交换。
1 2 3 4 5 | 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有一种更好的方法来进行交换,这将使代码像这样
1 2 3 | 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] |
这里是一个基于
1 2 3 4 5 6 7 8 9 10 | 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))) |
1 | newList = [(x[2*i+1], x[2*i]) for i in range(0, len(x)/2)] |
现在找到一个解压元组的方法。我不会做你所有的家庭作业。