Replace all occurrences in string but the first one

Viewed 606
7 Answers

str.partition splits a string into the part before a delimiter, the delimiter itself, and the part after, or the string and two empty strings if the delimiter doesn’t exist. What that comes down to is:

s = 'X did something. X found it to be good, and so X went home.'
before, first, after = s.partition('X')
result = before + first + after.replace('X', 'Y')

You cold use the fact that re.sub uses a function:

import re


def repl(match, count=[0]):
    x, = count
    count[0] += 1
    if x > 0:
        return 'Y'
    return 'X'


print(re.sub('X', repl, 'X did something. X found it to be good, and so X went home.'))

Output

X did something. Y found it to be good, and so Y went home.

The idea is to use a function that keeps the count of seen X and then replace it when the count if above 1.

Another Option is to find the first one and only after replace all X occurrences.

Finally, concat the beginning to the start of the sentence

st = 'X did something. X found it to be good, and so X went home.'
first_found = st.find('X')
print (st[:first_found + 1] + st[first_found + 1:].replace('X', 'Y'))
# X did something. Y found it to be good, and so Y went home.

Here's a low tech solution without regex. :)

>>> s = 'X did something. X found it to be good, and so X went home'
>>> s = s.replace('X', 'Y').replace('Y', 'X', 1)
>>> s
>>> 'X did something. Y found it to be good, and so Y went home'

Solution if 'Y' can exist in the original string:

def replace_tail(s, target, replacement):
    try:
        pos = s.index(target)
    except ValueError:
        return s
    pos += len(target)
    head = s[:pos]
    tail = s[pos:]
    return head + tail.replace(target, replacement)

Demo:

>>> s = 'Today YYY and XXX did something. XXX found it to be good, and so XXX went home without YYY.'
>>> replace_tail(s, 'XXX', 'YYY')
>>> 'Today YYY and XXX did something. YYY found it to be good, and so YYY went home without YYY.'

Apply iteratively the regex after finding the first match over the remaining of the string. Or just using replace if it is possible.

We can use slicing to produce two string: first one up to (and including) the first element, and the next slice that contains the rest. We can then apply the replace part on that part, and merge these back:

def replace_but_first(text, search, replace):
    try:
        idx = text.index(search) + len(search)
        return text[:idx] + text[idx:].replace(search, replace)
    except ValueError:  # we did not found a single match
        return text

For example:

>>> replace_but_first('X did something. X found it to be good, and so X went home.', 'X', 'Y')
'X did something. Y found it to be good, and so Y went home.'

If you are still interested in using regular expression operations, you can use re.finditer(). This returns an iterator yielding MatchObject instances of each match case found. Casting the iterator to a list allows you to index into the MatchObject instances. In the function below, [1:] indicates to skip the first match.

def replace_rest(my_string, replacement):

    for match in list(re.finditer(r'(X)', my_string))[1:]:
        my_string = my_string[0:match.start()] + replacement + my_string[match.end():]

    return my_string

Run:

>>> my_string = "Person X did something. X found it to be good, and so Y went home."

Output:

>>> replace_rest(my_string, "Y")
'Person X did something. Y found it to be good, and so Y went home.'

Side Note: This can also useful for ignoring any n^th occurrence of a pattern.

Related