Better way to swap elements in a list?

Question:

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?

Asked By: EliseB

||

Answers:

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]
Answered By: Matthias

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.

Answered By: NPE

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]
Answered By: alecxe
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.

Answered By: Jossie Calderon

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.

Answered By: Chris_Rands

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)))
Answered By: LarsVegas

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 🙂

Answered By: Anzel

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)]
Answered By: Moinuddin Quadri

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]
Answered By: be_good_do_good

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)))
Answered By: Mazdak

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]
Answered By: Jared Goguen

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])
Answered By: Kevin

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())
Answered By: user120404

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]
Answered By: Piyush Rastogi
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.