Better way to swap elements in a list?

后端 未结 14 1252
臣服心动
臣服心动 2020-12-23 13:32

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:

fi         


        
相关标签:
14条回答
  • 2020-12-23 14:12

    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)))
    
    0 讨论(0)
  • 2020-12-23 14:13

    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]
    
    0 讨论(0)
  • 2020-12-23 14:16

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

    0 讨论(0)
  • 2020-12-23 14:19

    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.

    0 讨论(0)
  • 2020-12-23 14:21

    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]
    
    0 讨论(0)
  • 2020-12-23 14:23

    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)))
    
    0 讨论(0)
提交回复
热议问题