203

Is there a quick way in Python to replace strings but, instead of starting from the beginning as replace does, starting from the end? For example:

>>> def rreplace(old, new, occurrence)
>>>     ... # Code to replace the last occurrences of old by new

>>> '<div><div>Hello</div></div>'.rreplace('</div>','</bad>',1)
>>> '<div><div>Hello</div></bad>'
Cœur
  • 37,241
  • 25
  • 195
  • 267
Barthelemy
  • 8,277
  • 6
  • 33
  • 36
  • 9
    Good question, judging by the complicated solutions to such a simple problem. – Justin Ardini Mar 31 '10 at 20:22
  • 7
    There is an elegant one-liner in the answers below that took 9 years(!) to get added to this question, simply scroll down to find it. – Phil B Jan 04 '20 at 08:53

8 Answers8

255
>>> def rreplace(s, old, new, occurrence):
...  li = s.rsplit(old, occurrence)
...  return new.join(li)
... 
>>> s
'1232425'
>>> rreplace(s, '2', ' ', 2)
'123 4 5'
>>> rreplace(s, '2', ' ', 3)
'1 3 4 5'
>>> rreplace(s, '2', ' ', 4)
'1 3 4 5'
>>> rreplace(s, '2', ' ', 0)
'1232425'
mg.
  • 7,822
  • 1
  • 26
  • 30
  • 11
    Very nice! In an unscientific benchmark of replacing the last occurrence of an expression in a typical string in my program (> 500 characters), your solution was three times faster than Alex's solution and four times faster than Mark's solution. Thanks to all for your answers! – Barthelemy Mar 31 '10 at 20:44
  • 2
    Thanks, it works. The [`.replace`](https://docs.python.org/3.3/library/stdtypes.html#str.replace) method takes a third optional argument 'count' which tells it to replace the first n occurrences. Would it not have been intuitive if that would take a -1 like but unfortunately it won't, so we need your solution. – cardamom Dec 03 '18 at 10:38
81

Here is a one-liner:

result = new.join(s.rsplit(old, maxreplace))

Return a copy of string s with all occurrences of substring old replaced by new. The first maxreplace occurrences are replaced.

and a full example of this in use:

s = 'mississipi'
old = 'iss'
new = 'XXX'
maxreplace = 1

result = new.join(s.rsplit(old, maxreplace))
>>> result
'missXXXipi'
Phil B
  • 5,589
  • 7
  • 42
  • 58
  • 2
    Nice! Used it to remove trailing commas from lines: `line = "".join(line.rsplit(",", 1))` while keeping padding space afterwards. – grandchild Dec 27 '19 at 13:31
  • what can be modified in here to use it on a dataframe column? – RSM Sep 27 '20 at 18:29
23

I'm not going to pretend that this is the most efficient way of doing it, but it's a simple way. It reverses all the strings in question, performs an ordinary replacement using str.replace on the reversed strings, then reverses the result back the right way round:

>>> def rreplace(s, old, new, count):
...     return (s[::-1].replace(old[::-1], new[::-1], count))[::-1]
...
>>> rreplace('<div><div>Hello</div></div>', '</div>', '</bad>', 1)
'<div><div>Hello</div></bad>'
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
16

Just reverse the string, replace first occurrence and reverse it again:

mystr = "Remove last occurrence of a BAD word. This is a last BAD word."

removal = "BAD"
reverse_removal = removal[::-1]

replacement = "GOOD"
reverse_replacement = replacement[::-1]

newstr = mystr[::-1].replace(reverse_removal, reverse_replacement, 1)[::-1]
print ("mystr:", mystr)
print ("newstr:", newstr)

Output:

mystr: Remove last occurence of a BAD word. This is a last BAD word.
newstr: Remove last occurence of a BAD word. This is a last GOOD word.
Joe
  • 11,983
  • 31
  • 109
  • 183
  • when i use this, the format of digital book page never changed whereas using 'join' fuction changed the format. great! – info-farmer Dec 02 '20 at 01:40
5

If you know that the 'old' string does not contain any special characters you can do it with a regex:

In [44]: s = '<div><div>Hello</div></div>'

In [45]: import re

In [46]: re.sub(r'(.*)</div>', r'\1</bad>', s)
Out[46]: '<div><div>Hello</div></bad>'
Dave Kirby
  • 25,806
  • 5
  • 67
  • 84
2

Here is a recursive solution to the problem:

def rreplace(s, old, new, occurence = 1):

    if occurence == 0:
        return s

    left, found, right = s.rpartition(old)

    if found == "":
        return right
    else:
        return rreplace(left, old, new, occurence - 1) + new + right
naivnomore
  • 1,291
  • 8
  • 14
1

Try this:

def replace_last(string, old, new):
    old_idx = string.rfind(old)
    return string[:old_idx] + new + string[old_idx+len(old):]

Similarly you can replace first occurrence by replacing string.rfind() with string.find().

I hope it helps.

Nomi
  • 185
  • 2
  • 13
0

If you have a list of strings you can use list comprehension and string slicing in a one liner to cover the whole list.. No need to use a function;

myList = [x[::-1].replace('<div>'[::-1],'<bad>'[::-1],1)[::-1] if x.endswith('<div>') else x for x in myList]

I use if else to keep the items in the list that don't meet the criteria for replacement otherwise your list would just be the items that do meet the criteria.

ehambright
  • 1,416
  • 19
  • 27