79

I want to do a string replace in Python, but only do the first instance going from right to left. In an ideal world I'd have:

myStr = "mississippi"
print myStr.rreplace("iss","XXX",1)

> missXXXippi

What's the best way of doing this, given that rreplace doesn't exist?

codeforester
  • 39,467
  • 16
  • 112
  • 140
fredley
  • 32,953
  • 42
  • 145
  • 236
  • 6
    rel: http://stackoverflow.com/q/2556108/989121 – georg Mar 30 '12 at 13:11
  • 1
    Possible duplicate of [rreplace - How to replace the last occurrence of an expression in a string?](https://stackoverflow.com/questions/2556108/rreplace-how-to-replace-the-last-occurrence-of-an-expression-in-a-string) – gdvalderrama Mar 22 '18 at 08:01

8 Answers8

115

rsplit and join could be used to simulate the effects of an rreplace

>>> 'XXX'.join('mississippi'.rsplit('iss', 1))
'missXXXippi'
Stephen Emslie
  • 10,539
  • 9
  • 32
  • 28
  • 7
    +1 for a solution that's readily understandable by programers that follow. – strongMA Oct 23 '13 at 22:30
  • 1
    As opposed to other solutions, this one is also easily extensible to execute multiple replacements from the right. – Anaphory Sep 27 '15 at 09:46
  • 1
    If you want to replace known extension or just remove/append something from/to end you can do it even simpler `yourFilepath.rsplit('.py', 1)[0] + '.log'` :) – jave.web Feb 25 '20 at 16:02
  • I wonder why didn't python teams write a specific method to replace strings from right to left? – Hzzkygcs Sep 03 '20 at 18:31
  • 2
    @jave.web or starting with 3.9 `yourFilepath.removesuffix('.py') + '.log'` – Christian May 11 '22 at 17:05
26
>>> myStr[::-1].replace("iss"[::-1], "XXX"[::-1], 1)[::-1]
'missXXXippi'
eumiro
  • 207,213
  • 34
  • 299
  • 261
17
>>> re.sub(r'(.*)iss',r'\1XXX',myStr)
'missXXXippi'

The regex engine cosumes all the string and then starts backtracking untill iss is found. Then it replaces the found string with the needed pattern.


Some speed tests

The solution with [::-1] turns out to be faster.

The solution with re was only faster for long strings (longer than 1 million symbols).

ovgolovin
  • 13,063
  • 6
  • 47
  • 78
  • Is this more efficient than the reversal method for short strings (<20 chars)? – fredley Mar 30 '12 at 13:13
  • @TomMedley I don't know. I didn't do any speed tests. As I know, backtracking is quite slow. And if `iss` is somewhere in the beginning of the string, it'll take quite a while to bracktrack until the engine finds a position where `iss` matches. – ovgolovin Mar 30 '12 at 13:15
  • @TomMedley But I don't think that those `[::-1]` in the other solutions are faster :) – ovgolovin Mar 30 '12 at 13:17
  • It's always the last few chars of the string actually – fredley Mar 30 '12 at 13:23
  • @TomMedley If I were concerned with the speed issues, I would test all the solutions with `timeit` module. – ovgolovin Mar 30 '12 at 13:27
  • 1
    @TomMedley - this `re` solution is in this special case about 9 times slower than mine with `[::-1]` (12.6µs vs 1.36µs). – eumiro Mar 30 '12 at 13:41
  • @eumiro Yeah. I thought that it may be connected with regexp compilation. But it turned out to be more slow even if used with precompiled pattern. – ovgolovin Mar 30 '12 at 13:49
  • For short strings you really, really shouldn't be worrying about speed! The regexp solution is faster to write, read and debug (for anyone familiar with regular expressions). It is also the more general approach, since you can use it with more complex replacements. Unless your code will be doing millions of string edits per minute, the regexp is by far the better solution. – alexis Mar 30 '12 at 14:26
16

you may reverse a string like so:

myStr[::-1]

to replace just add the .replace:

print myStr[::-1].replace("iss","XXX",1)

however now your string is backwards, so re-reverse it:

myStr[::-1].replace("iss","XXX",1)[::-1]

and you're done. If your replace strings are static just reverse them in file to reduce overhead. If not, the same trick will work.

myStr[::-1].replace("iss"[::-1],"XXX"[::-1],1)[::-1]
Serdalis
  • 10,296
  • 2
  • 38
  • 58
3
def rreplace(s, old, new):
    try:
        place = s.rindex(old)
        return ''.join((s[:place],new,s[place+len(old):]))
    except ValueError:
        return s
Anaphory
  • 6,045
  • 4
  • 37
  • 68
2

You could also use str.rpartition() which splits the string by the specified separator from right and returns a tuple:

myStr = "mississippi"

first, sep, last = myStr.rpartition('iss')
print(first + 'XXX' + last)
# missXXXippi
Austin
  • 25,759
  • 4
  • 25
  • 48
0

Using the package fishhook (available through pip), you can add this functionality.

from fishhook import hook

@hook(str)
def rreplace(self, old, new, count=-1):
    return self[::-1].replace(old[::-1], new[::-1], count)[::-1]

print('abxycdxyef'.rreplace('xy', '--', count=1))

# 'abxycd--ef'
-1

It's kind of a dirty hack, but you could reverse the string and replace with also reversed strings.

"mississippi".reverse().replace('iss'.reverse(), 'XXX'.reverse(),1).reverse()
sleeplessnerd
  • 21,853
  • 1
  • 25
  • 29