28

I want to replace a sub-list from list a, with another sub-list. Something like this:

a=[1,3,5,10,13]

Lets say I want to take a sublist like:

a_sub=[3,5,10]

and replace it with

b_sub=[9,7]

so the final result will be

print(a) 
>>> [1,9,7,13]

Any suggestions?

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
darxyde
  • 283
  • 1
  • 3
  • 5
  • I havent tried anything, because i have no idea how to do it. I have been searching on stackoverflow for about 2 hours for ideas. – darxyde Oct 15 '12 at 14:43
  • 2
    Do you know the position of `a_sub` in `a`? Will `a_sub` always be in `a`? – sloth Oct 15 '12 at 14:46
  • 1
    I don't know the position of `a_sub` in `a`, and `a_sub` will always be in `a`. – darxyde Oct 15 '12 at 14:51

4 Answers4

26
In [39]: a=[1,3,5,10,13]

In [40]: sub_list_start = 1

In [41]: sub_list_end = 3

In [42]: a[sub_list_start : sub_list_end+1] = [9,7]

In [43]: a
Out[43]: [1, 9, 7, 13]

Hope that helps

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
16

You can do this nicely with list slicing:

>>> a=[1, 3, 5, 10, 13]
>>> a[1:4] = [9, 7]
>>> a
[1, 9, 7, 13]

So how do we get the indices here? Well, let's start by finding the first one. We scan item by item until we find a matching sublist, and return the start and end of that sublist.

def find_first_sublist(seq, sublist, start=0):
    length = len(sublist)
    for index in range(start, len(seq)):
        if seq[index:index+length] == sublist:
            return index, index+length

We can now do our replacement - we start at the beginning, replace the first one we find, and then try to find another after our newly finished replacement. We repeat this until we can no longer find sublists to replace.

def replace_sublist(seq, sublist, replacement):
    length = len(replacement)
    index = 0
    for start, end in iter(lambda: find_first_sublist(seq, sublist, index), None):
        seq[start:end] = replacement
        index = start + length

Which we can use nicely:

>>> a=[1, 3, 5, 10, 13]
>>> replace_sublist(a, [3, 5, 10], [9, 7])
>>> a
[1, 9, 7, 13]
Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • I think your `find_sublist` function can be simplified to `next(((i,i+length) for i,x in enumerate(seq) if seq[i:i+length] == sublist), None)`. But depending on the use case we might need to be a little craftier about how the replacement process works (say you want to repeatedly replace something with something that contains itself.) – DSM Oct 15 '12 at 14:58
  • [This produces wrong results due to rescanning replaced sections of the list.](https://ideone.com/fy3L8K) – user2357112 Jan 29 '18 at 18:55
  • @user2357112 Nice catch - the fix is to ensure `find_sublists()` is computed before doing replacement, rather than as we iterate. [Wrapping the call with `list()` does this](https://ideone.com/q3Ecft). Edited to fix. – Gareth Latty Jan 30 '18 at 02:18
  • There are still a few more tricky considerations. First, [replacing one instance of `target` shifts the indices of further instances, but `replace_sublist` still uses the original indices](https://ideone.com/ilXBi3). Second, [overlapping matches produce overlapping replacements](https://ideone.com/NvPtjo) instead of the more familiar behavior from substring replacement functions where matches that start inside a previous match aren't considered. I think that's everything. – user2357112 Jan 30 '18 at 02:39
  • @user2357112 You are correct, I was too quick to assume that'd fix things. The issue is there is no way you can separate the replacement while operating in-place, as it will always alter the string and change the situation. The new version seems to work for all the cases you've given. – Gareth Latty Jan 30 '18 at 23:56
2

You need to take a slice from start_index to end_index + 1, and assign your sublist to it.

Just like you can do: - a[0] = 5, you can similarly assign a sublist to your slice: - a[0:5] -> Creates a slice from index 0 to index 4

All you need is to find out the position of the sublist you want to substitute.

>>> a=[1,3,5,10,13]

>>> b_sub = [9, 7]

>>> a[1:4] = [9,7]  # Substitute `slice` from 1 to 3 with the given list

>>> a
[1, 9, 7, 13]
>>> 

As you can see that, substituted sublist don't have to be of same length of the substituting sublist.

In fact you can replace, 4 length list with 2 length list and vice-versa.

Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • The interesting part of the question is finding arbitrary length sublists in arbitrary length lists. Without busting the stack. – Chris Wesseling Oct 15 '12 at 14:58
0

Here is another way to do this. This method works if we need to replaces more than one sublist:

a=[1,3,5,10,13]
a_sub=[3,5,10]
b_sub=[9,7]

def replace_sub(a, a_sub, b_sub):
    a_str = ',' + ','.join(map(str, a)) + ','
    a_sub_str = ',' + ','.join(map(str, a_sub)) + ','
    b_sub_str = ',' + ','.join(map(str, b_sub)) +','

    replaced_str = a_str.replace(a_sub_str, b_sub_str)[1 : -1]

    return map(int, replaced_str.split(','))

Result:

>>> replace_sub(a, a_sub, b_sub)
[1, 9, 7, 13]
>>> replace_sub([10, 13, 4], [3, 4], [7])
[10, 13, 4] #[3,4] is not in the list so nothing happens

Replace more than one sublist:

>>> a=[1,3,5,10,13,3,5,10]
>>> a_sub=[3,5,10]
>>> b_sub=[9,7]
>>> replace_sub(a, a_sub, b_sub)
[1, 9, 7, 13, 9, 7]
Akavall
  • 82,592
  • 51
  • 207
  • 251