49

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
EliseB
  • 639
  • 1
  • 9
  • 15

14 Answers14

113

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 :-)

Community
  • 1
  • 1
Anzel
  • 19,825
  • 5
  • 51
  • 52
  • 2
    @MoinuddinQuadri, thanks and this multiple assignment approach will update the list inplace. In case you want a new list, other answers like alexce is the way to go, or you can assign a new list first, ie. `a = l[:]; a[::2], a[1::2] = a[1::2], a[::2]` – Anzel Aug 26 '16 at 13:44
  • 2
    I would still add a comment to this code. Pretty hard to look at. – jpmc26 Aug 27 '16 at 00:55
  • 1
    @jpmc26, an explanation added -- hopefully this doesn't make it worse :-) or please help edit the explanation and make it clearer to others. – Anzel Aug 27 '16 at 02:18
  • 2
    This is also a lot faster (4x) than most of the other solutions, for 100k elements on my Core2Duo with Python2.7 (Ubuntu15.10 x86-64). It doesn't make any mmap/munmap system calls while running, so it's actually swapping in-place instead of making the interpreter allocate and free scratch memory. (Kasramvd's now-deleted answer with the benchmark had a mistake: it was timing the same thing all 4 times. The code was useful after I fixed that bug. Hopefully s/he'll undelete it as a useful speed-test comparison answer.) – Peter Cordes Aug 27 '16 at 06:51
  • 1
    Awesome thinking and simple, but great technique. Loved the approach to the problem. – be_good_do_good Aug 27 '16 at 11:25
  • 1
    Gotta say, this blows my mind. I was doing a pixel-swap (RGBA -> BGRA) by selecting quads from a list, then doing a `newlist.extend(quad)`, and it was slow (well, only when doing a million of them). I switched to `pixel_data[::4], pixel_data[2::4] = pixel_data[2::4], pixel_data[::4]`, and it was an *order of magnitude* faster. Maybe more, it didn't even show up on the profiler after that. Guess I need to study slicing more, didn't realize it could do this. – John C Sep 29 '21 at 16:23
36

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.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 1
    this is code golfing and i approve ☺. Typo on "would", you must mean "wouldn't". The only problem with it is that it doesn't work with odd number of list entries – Ma0 Aug 26 '16 at 13:16
  • 2
    This is really clever. – laike9m Aug 31 '16 at 14:33
  • I am new to Python but what does In [1] and Out [2] mean? Why is it needed and is there a name for this syntax? – Jumpman Oct 21 '22 at 07:48
20

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]
Community
  • 1
  • 1
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
11

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)))
Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • 1
    `stmt=inp1` in all four blocks. After fixing that, I see 53.8s / 19.9s / 19.6s / 5.45s, so your method is actually the slowest :(, and @Anzel's is by far the fastest (Intel Core2Duo E6600 2.4GHz, python 2.7 from x86-64 Ubuntu 15.10). Your times (for the same thing 4 times) might reflect [Intel Turbo clock-speed boost during the first ~20 seconds of the first run](https://en.wikipedia.org/wiki/Intel_Turbo_Boost), then settling down to the max steady-state clock speed for the rest. You also didn't say what hardware you tested on, e.g. Intel i5-4570 @ 3.6GHz (Haswell) with DDR3-1600 RAM. – Peter Cordes Aug 27 '16 at 02:35
  • (Depending how efficiently Python stores a 100k element list, it might not fit entirely in cache, so mem perf might matter.) Amusingly, making a shuffled copy (or in-place shuffle) of an array of integers is something you could do *blazingly* fast in assembly language, or C with SSE/AVX intrinsics. Load a vector of 32 bytes, apply a compile-time-constant shuffle to the elements with [`pshufd`](http://www.felixcloutier.com/x86/PSHUFD.html), then store a vector of 32 bytes. That should run at essentially the same speed as memcpy. – Peter Cordes Aug 27 '16 at 02:38
  • @PeterCordes That was a terrible mistake and I definitely agree with you about the benchmarks, I think still there is no need for my solution because it's pretty obvious that my code will take longer than the rest, I don't know why on earth I used 2 for loop (which means more unpacking, complexity, calls, stack jobs, etc. ), while I've wrote in my answer that other ones are using multiple iteration, slicing, etc. and now I think the only interesting thing might be a benchmark between those three answers. – Mazdak Aug 27 '16 at 13:10
  • 2
    Nice update. I ran these under `strace` and noticed that Anzel's version, unlike the others, produced no mmap/munmap calls while running. So it's operating in-place without allocating and destroying temporary copies. This is part of why it's so much faster. I also timed the C-like `for i in range(0, len(l), 2): l[i], l[i+1] = l[i+1], l[i]` from Matthias's answer, and found it faster than NPE's or alexce's, but 3x slower than Anzel's. Of course, it's so non-Pythonic that most people wouldn't want to use it anyway. – Peter Cordes Aug 27 '16 at 20:55
  • @PeterCordes Indeed these are what I call smart optimizations, and python is fill to the brim with such optimizations. – Mazdak Aug 27 '16 at 21:16
  • Nice benchmark, in case anyone is curious I repeated the benchmark with my solution below (`inp4`), but I found mine is the slowest, here are the timings I got: inp1 -> 7.8195558990119025 inp2a -> 5.318918139004381 inp2b -> 3.2198629069898743 inp3 -> 1.284413435991155 inp4 -> 9.924097775976406 – Chris_Rands Apr 06 '17 at 13:20
3

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.

Community
  • 1
  • 1
Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
3

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)]
Moinuddin Quadri
  • 46,825
  • 13
  • 96
  • 126
1

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]
be_good_do_good
  • 4,311
  • 3
  • 28
  • 42
1

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]
Jared Goguen
  • 8,772
  • 2
  • 18
  • 36
1

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])
Kevin
  • 685
  • 1
  • 7
  • 16
0

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())
user120404
  • 111
  • 2
0

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]
-2

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]
Matthias
  • 3,160
  • 2
  • 24
  • 38
  • 2
    You don't need the temporary variables to swap in Python. Also, this is no better than OP's solution. – Morgan Thrapp Aug 26 '16 at 13:15
  • @MorganThrapp noted. I agree. – Matthias Aug 26 '16 at 13:16
  • 1
    This is how you'd write it in C, and makes it easy to understand for C programmers. I'm not surprised that iterating over list elements "manually" is considered ugly, though. It's also 3x slower than Anzel's accepted answer on my Core2Duo (but a bit faster than some of the other suggestions). (See Kasramvd's deleted answer: testing 100 times with a list with 100k elements). – Peter Cordes Aug 27 '16 at 06:18
-4
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.

Jossie Calderon
  • 1,393
  • 12
  • 21
  • 1
    as a bonus, then if there are an odd number of elements, the last is simply ignored... – Aaron Aug 26 '16 at 13:30
-4

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)))
LarsVegas
  • 6,522
  • 10
  • 43
  • 67