更好的方式来交换列表中的元素?
我有一堆看起来像这样的列表:
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]
TLDR;
编辑解释
我相信大多数观众已经熟悉列表切片和多重任务。 如果你不这样做,我会尽我所能来解释发生了什么(希望我不会变得更糟)。
为了理解列表切片, 这里已经有了一个很好的答案和列表切片的解释。 简单的说:
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
所做的就是翻转我最不重要的位,有效地将1与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和Python 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
)的性能增加。 正如你可以看到它和最后一个解决scheme之间的差异已经减less到〜2秒(从〜7秒)。 原因是因为python3.X中的zip()
函数返回一个按需生成项目的迭代器。 而且由于chain.from_iterable()
需要重复遍历这些项目,所以在这之前完成它是完全多余的(在python 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)))
另一种方法是创build嵌套列表,使用对颠倒它们的顺序,然后用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的基准testing ,我的解决scheme,我发现这个解决scheme比其他答案慢,所以我不会推荐它的大型列表。 尽pipe性能并不重要,但我仍然觉得这很可读。
使用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”解释为“reverse”, 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]
(其他)select:
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])
根本没有看到你的实现有什么问题。 但是你也许可以做一个简单的交换。
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)]
现在find解压元组的方法。 我不会做所有的功课。
这里基于modulo
运算符的解决scheme:
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)))