Better way to swap elements in a list?
PythonPython Problem Overview
I have a bunch of lists that look like this one:
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
I want to swap elements as follows:
final_l = [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
The size of the lists may vary, but they will always contain an even number of elements.
I'm fairly new to Python and am currently doing it like this:
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])
I know this isn't really Pythonic and would like to use something more efficient. Maybe a list comprehension?
Python Solutions
Solution 1 - Python
No need for complicated logic, simply rearrange the list with slicing and step:
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;
Edited with explanation
I believe most viewers are already familiar with list slicing and multiple assignment. In case you don't I will try my best to explain what's going on (hope I do not make it worse).
To understand list slicing, here already has an excellent answer and explanation of list slice notation. Simply put:
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
Let's look at OP's requirements:
[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
First tier will be: l[::2] = [1, 3, 5, 7, 9]
Second tier will be: l[1::2] = [2, 4, 6, 8, 10]
As we want to re-assign first = second
& second = first
, we can use multiple assignment, and update the original list in place:
first , second = second , first
that is:
l[::2], l[1::2] = l[1::2], l[::2]
As a side note, to get a new list but not altering original l
, we can assign a new list from l
, and perform above, that is:
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]
Hopefully I do not confuse any of you with this added explanation. If it does, please help update mine and make it better :-)
Solution 2 - Python
Here a single list comprehension that does the trick:
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]
The key to understanding it is the following demonstration of how it permutes the list indices:
In [3]: [i^1 for i in range(10)]
Out[3]: [1, 0, 3, 2, 5, 4, 7, 6, 9, 8]
The ^
is the exclusive or operator. All that i^1
does is flip the least-significant bit of i
, effectively swapping 0 with 1, 2 with 3 and so on.
Solution 3 - Python
You can use the pairwise iteration and chaining to flatten the list:
>>> from itertools import chain
>>>
>>> list(chain(*zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Or, you can use the itertools.chain.from_iterable()
to avoid the extra unpacking:
>>> list(chain.from_iterable(zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Solution 4 - Python
A benchmark between top answers:
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
If you are curious about the different performances between python 2 and 3, here are the reasons:
As you can see @NPE's answer (inp1
) performs very better in python3.4, the reason is that in python3.X range()
is a smart object and doesn't preserve all the items between that range in memory like a list.
> 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.
And that's why in python 3 it doesn't return a list while you slice the range object.
# python2.7
>>> range(10)[2:5]
[2, 3, 4]
# python 3.X
>>> range(10)[2:5]
range(2, 5)
The second significant change is performance accretion of the third approach (inp3
). As you can see the difference between it and the last solution has decreased to ~2sec (from ~7sec). The reason is because of the zip()
function which in python3.X it returns an iterator which produces the items on demand. And since the chain.from_iterable()
needs to iterate over the items once again it's completely redundant to do it before that too (what that zip
does in python 2).
Code:
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)))
Solution 5 - Python
Another way, create nested lists with pairs reversing their order, then flatten the lists with 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]
EDIT: I just applied Kasramvd's benchmark test to my solution and I found this solution is slower than the other top answers, so I wouldn't recommend it for large lists. I still find this quite readable though if performance is not critical.
Solution 6 - Python
One of the possible answer using chain
and 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)]
Solution 7 - Python
Another approach with simply re-assigning and slicing technique
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
output
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Solution 8 - Python
For fun, if we interpret "swap" to mean "reverse" in a more general scope, the itertools.chain.from_iterable
approach can be used for subsequences of longer lengths.
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]
Solution 9 - Python
An(other) alternative:
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
This assumes that the original list has an even number of items. If not, a try-except can be added:
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])
Solution 10 - Python
A way using 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())
Solution 11 - Python
New to stack overflow. Please free to leave a comment or feedback on this solution. swap = [2, 1, 4, 3, 5]
lst = []
for index in range(len(swap)):
if index%2 == 0 and index < len(swap)-1:
swap[index],swap[index+1] = swap[index+1],swap[index]
lst.append(swap[index])
print(lst)
out = [1, 2, 3, 4, 5]
Solution 12 - Python
I don't see anything wrong with your implementation at all. But you could perhaps do a simple swap instead.
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
EDIT Apparently, Python has a nicer way to do a swap which would make the code like this
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]
Solution 13 - Python
newList = [(x[2*i+1], x[2*i]) for i in range(0, len(x)/2)]
Now find a way to unzip the tuples. I won't do all of your homework.
Solution 14 - Python
Here a solution based in the modulo
operator:
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)))