交换列表中元素的更好方法是什么?

Eli*_*seB 50 python

我有一堆看起来像这样的列表:

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Run Code Online (Sandbox Code Playgroud)

我想交换元素如下:

final_l = [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Run Code Online (Sandbox Code Playgroud)

列表的大小可能会有所不同,但它们总是包含偶数个元素.

我是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])
Run Code Online (Sandbox Code Playgroud)

我知道这不是真正的Pythonic,并且想要使用更高效的东西.也许列表理解?

Anz*_*zel 106

无需复杂的逻辑,只需使用切片和步骤重新排列列表:

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]
Run Code Online (Sandbox Code Playgroud)

 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
Run Code Online (Sandbox Code Playgroud)

让我们看看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
Run Code Online (Sandbox Code Playgroud)

第一层将是:l[::2] = [1, 3, 5, 7, 9] 第二层将是:l[1::2] = [2, 4, 6, 8, 10]

由于我们要重新分配first = second&second = first,我们可以使用多个分配,并更新原始列表:

first , second  = second , first
Run Code Online (Sandbox Code Playgroud)

那是:

l[::2], l[1::2] = l[1::2], l[::2]
Run Code Online (Sandbox Code Playgroud)

作为旁注,要获得新列表但不改变原始列表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]
Run Code Online (Sandbox Code Playgroud)

希望我不要把你们这些额外的解释混淆.如果有,请帮助更新我的并使其更好:-)

  • @MoinuddinQuadri,谢谢,这种多重分配方法将在现场更新列表.如果你想要一个新的列表,其他的答案,如alexce是要走的路,或者你可以先分配一个新的列表,即.`a = l [:]; a [:: 2],a [1 :: 2] = a [1 :: 2],a [:: 2]` (2认同)
  • 我仍然会对此代码添加注释.很难看. (2认同)
  • 对于使用 Python2.7 (Ubuntu15.10 x86-64) 的 Core2Duo 上的 10 万个元素,这也比大多数其他解决方案快得多(4 倍)。它在运行时不会进行任何 mmap/munmap 系统调用,因此它实际上是就地交换,而不是让解释器分配和释放暂存内存。(Kasramvd 现在已删除的基准答案有一个错误:它对所有 4 次都进行了计时。在我修复了该错误后,该代码很有用。希望他/她能将其作为有用的速度测试比较答案取消删除。 ) (2认同)

NPE*_*NPE 34

这里有一个列表理解,可以解决这个问题:

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]
Run Code Online (Sandbox Code Playgroud)

理解它的关键是以下演示如何置换列表索引:

In [3]: [i^1 for i in range(10)]
Out[3]: [1, 0, 3, 2, 5, 4, 7, 6, 9, 8]
Run Code Online (Sandbox Code Playgroud)

^异或操作.所有这一切i^1都是翻转最不重要的一点i,有效地将1,2与3交换为0,依此类推.

  • 这真的很聪明. (2认同)

ale*_*cxe 19

您可以使用成对迭代和链接来展平列表:

>>> from itertools import chain
>>>
>>> list(chain(*zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用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]
Run Code Online (Sandbox Code Playgroud)


Kas*_*mvd 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
Run Code Online (Sandbox Code Playgroud)

Python 3.4:

inp1 -> 7.913498195000102
inp2a -> 9.680125927000518
inp2b -> 4.728151862000232
inp3 -> 3.1804273489997286
Run Code Online (Sandbox Code Playgroud)

如果你对python 2和3之间的不同表现感到好奇,原因如下:

正如你所看到的,@ NPE的answer(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)
Run Code Online (Sandbox Code Playgroud)

第二个重大变化是第三种方法的表现增加(inp3).正如您所看到的,它与最后一个解决方案之间的差异已减少到约2秒(从~7秒).原因是因为zip()python3.X 中的函数返回了一个按需生成项的迭代器.而且由于chain.from_iterable()需要再次迭代这些项目,所以在此之前完成它也是完全多余的(zip在python 2 中做了什么).

码:

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)))
Run Code Online (Sandbox Code Playgroud)

  • 很高兴看到一些比较基准. (4认同)
  • 不错的更新.我在`strace`下运行这些,并注意到Anzel的版本与其他版本不同,在运行时没有产生mmap/munmap调用.所以它在没有分配和销毁临时副本的情况下就地运行.这是为什么它如此快得多的部分原因.我还在范围内(0,len(l),2)计算了类似C的`for i:l [i],l [i + 1] = l [i + 1],l [i]`来自Matthias的答案,发现它比NPE或alexce更快,但比Anzel的慢3倍.当然,它是如此非Pythonic,大多数人不管怎样都不想使用它. (2认同)