so.very.tired
so.very.tired

Reputation: 3086

Swapping two sublists in a list

Given the following list:

my_list=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

I want to be able to swap the sub-list my_list[2:4] with the sub-list my_list[7:10] as quickly and as efficiently as possible, to get the new list:

new_list=[0, 1, 7, 8, 9, 4, 5, 6, 2, 3, 10, 11, 12]

Here's my attempt:

def swap(s1, s2, l):

    seg1=l[:s1.start]+l[s2]
    seg2=l[s1.stop : s2.start]
    seg3=l[s1]+l[s2.stop:]

    return seg1+seg2+seg3


print swap(slice(2,4), slice(7,10), [0,1,2,3,4,5,6,7,8,9,10,11,12])

This does print the desired output, although this way of doing it looks awful to me.

Is there a more easy and elegant way of doing it, that will not create four new lists for every function call? (I plan to call this function a lot)

I don't mind (actually I'd prefer) changing the original list, rather than creating new instance every function call.

Upvotes: 21

Views: 2829

Answers (6)

fferri
fferri

Reputation: 18940

Slices can be assigned.

Two variables can be swapped with a, b = b, a.

Combine the two above::

>>> my_list[7:10], my_list[2:4] = my_list[2:4], my_list[7:10]
>>> my_list
[0, 1, 7, 8, 9, 4, 5, 6, 2, 3, 10, 11, 12]

Beware that - if slices have different sizes - the order is important: If you swap in the opposite order, you end up with a different result, because it will change first the initial items (lower indices), and then the higher index items (but those will be shifted in a different position by the first assignment).

Also, slices must not overlap.

Upvotes: 30

Martin Evans
Martin Evans

Reputation: 46759

Not exactly obvious (or efficient) but it works. I was curious to see if the slice object could be further utilised.

import itertools

def replace(s1, s2, l):
    lslice = [slice(0,s1.start), s2, slice(s1.stop, s2.start), s1, slice(s2.stop,len(l))]
    return list(itertools.chain.from_iterable([l[x] for x in lslice]))

Upvotes: 2

Joe T. Boka
Joe T. Boka

Reputation: 6589

This is an other way to do it:

import itertools
my_list=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
my_list2 = []
my_list2.extend((my_list[0:2],my_list[7:10],my_list[4:7],my_list[2:4],my_list[10:]))
new_list = list(itertools.chain.from_iterable(my_list2)

new_list print output:

[0, 1, 7, 8, 9, 4, 5, 6, 2, 3, 10, 11, 12]

Upvotes: 1

Will
Will

Reputation: 842

I think it's much better to use list index as parameters.

If you'd like to redefine your replace fuction like this:

my_list=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

def slice_replace(src_list, l_start, l_end, r_start, r_end):
    if l_end <= r_start:
        return src_list[:l_start] + src_list[r_start:r_end] + src_list[l_end:r_start] + src_list[l_start:l_end] + src_list[r_end:]
    else:
        return slice_replace(src_list, r_start, r_end, l_start, l_end)

print my_list
new_list = slice_replace(my_list, 2, 4, 7, 10)
print new_list

new_list = slice_replace(my_list, 7, 10, 2, 4)
print new_list

I have fixed it.

Upvotes: 0

interjay
interjay

Reputation: 110069

You can use normal swapping technique (x,y = y,x) here, but only if you perform the swap in the correct order: x must be the second (rightmost) slice, while y is the first (leftmost) slice.

>>> my_list=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
>>> my_list[7:10], my_list[2:4] = my_list[2:4], my_list[7:10]
>>> my_list
[0, 1, 7, 8, 9, 4, 5, 6, 2, 3, 10, 11, 12]

This works because it will assign to my_list[7:10] first, and only then to my_list[2:4].

If you perform this in the reverse order, assigning to my_list[2:4] first will change the location of the items on the right due to the sublists having different lengths, which will give incorrect results.

Performance-wise, this may or may not be faster than your code: it probably depends on the length of the list and the slices. You will have to test it on typical use-cases to see.

Upvotes: 6

SuperBiasedMan
SuperBiasedMan

Reputation: 9969

I think it's best to use concatenation and slicing. If you pass the list and then two lists with index pairs then you can just slice apart the list and rearrange the two sublists. Note that indexA and indexB both work in the some way as regular slicing, the start number is included but the end one is not.

def replace(source, indexA, indexB):
    newList = source[:indexA[0]] + source[indexB[0]:indexB[1]]
    newList += source[indexA[1]:indexB[0]] + source[indexA[0]:indexA[1]]
    newList += source[indexB[1]:]
    return newList

myList = replace(myList, [2,4], [7,10])

Upvotes: 2

Related Questions