Question :
Using python regular expression only, how to find and replace nth occurrence of word in a sentence?
For example:
str = 'cat goose mouse horse pig cat cow'
new_str = re.sub(r'cat', r'Bull', str)
new_str = re.sub(r'cat', r'Bull', str, 1)
new_str = re.sub(r'cat', r'Bull', str, 2)
I have a sentence above where the word ‘cat’ appears two times in the sentence. I want 2nd occurence of the ‘cat’ to be changed to ‘Bull’ leaving 1st ‘cat’ word untouched. My final sentence would look like:
“cat goose mouse horse pig Bull cow”. In my code above I tried 3 different times could not get what I wanted.
Answer #1:
Use negative lookahead like below.
>>> s = "cat goose mouse horse pig cat cow"
>>> re.sub(r'^((?:(?!cat).)*cat(?:(?!cat).)*)cat', r'1Bull', s)
'cat goose mouse horse pig Bull cow'
^
Asserts that we are at the start.(?:(?!cat).)*
Matches any character but not ofcat
, zero or more times.cat
matches the firstcat
substring.(?:(?!cat).)*
Matches any character but not ofcat
, zero or more times.- Now, enclose all the patterns inside a capturing group like
((?:(?!cat).)*cat(?:(?!cat).)*)
, so that we could refer those captured chars on later. cat
now the following secondcat
string is matched.
OR
>>> s = "cat goose mouse horse pig cat cow"
>>> re.sub(r'^(.*?(cat.*?){1})cat', r'1Bull', s)
'cat goose mouse horse pig Bull cow'
Change the number inside the {}
to replace the first or second or nth occurrence of the string cat
To replace the third occurrence of the string cat
, put 2
inside the curly braces ..
>>> re.sub(r'^(.*?(cat.*?){2})cat', r'1Bull', "cat goose mouse horse pig cat foo cat cow")
'cat goose mouse horse pig cat foo Bull cow'
Answer #2:
I use simple function, which lists all occurrences, picks the nth one’s position and uses it to split original string into two substrings. Then it replaces first occurrence in the second substring and joins substrings back into the new string:
import re
def replacenth(string, sub, wanted, n)
where = [m.start() for m in re.finditer(sub, string)][n-1]
before = string[:where]
after = string[where:]
after.replace(sub, wanted, 1)
newString = before + after
print newString
For these variables:
string = 'ababababababababab'
sub = 'ab'
wanted = 'CD'
n = 5
outputs:
ababababCDabababab
Notes:
The
where
variable actually is a list of matches’ positions, where you pick up the nth one. But list item index starts with0
usually, not with1
. Therefore there is an-1
index andn
variable is the actual nth substring. My example finds 5th string. If you usen
index and want to find 5th position, you’ll needn
to be4
. Which you use usually depends on the function, which generates ourn
.This should be the simplest way, but it isn’t regex only as you originally wanted.
Sources and some links in addition:
where
construction: Find all occurrences of a substring in Python- string splitting: https://www.daniweb.com/programming/software-development/threads/452362/replace-nth-occurrence-of-any-sub-string-in-a-string
- similar question: Find the nth occurrence of substring in a string
Answer #3:
Here’s a way to do it without a regex:
def replaceNth(s, source, target, n):
inds = [i for i in range(len(s) - len(source)+1) if s[i:i+len(source)]==source]
if len(inds) < n:
return # or maybe raise an error
s = list(s) # can't assign to string slices. So, let's listify
s[inds[n-1]:inds[n-1]+len(source)] = target # do n-1 because we start from the first occurrence of the string, not the 0-th
return ''.join(s)
Usage:
In [278]: s
Out[278]: 'cat goose mouse horse pig cat cow'
In [279]: replaceNth(s, 'cat', 'Bull', 2)
Out[279]: 'cat goose mouse horse pig Bull cow'
In [280]: print(replaceNth(s, 'cat', 'Bull', 3))
None
Answer #4:
I would define a function that will work for every regex:
import re
def replace_ith_instance(string, pattern, new_str, i = None, pattern_flags = 0):
# If i is None - replacing last occurrence
match_obj = re.finditer(r'{0}'.format(pattern), string, flags = pattern_flags)
matches = [item for item in match_obj]
if i == None:
i = len(matches)
if len(matches) == 0 or len(matches) < i:
return string
match = matches[i - 1]
match_start_index = match.start()
match_len = len(match.group())
return '{0}{1}{2}'.format(string[0:match_start_index], new_str, string[match_start_index + match_len:])
A working example:
str = 'cat goose mouse horse pig cat cow'
ns = replace_ith_instance(str, 'cat', 'Bull', 2)
print(ns)
The output:
cat goose mouse horse pig Bull cow
Another example:
str2 = 'abc abc def abc abc'
ns = replace_ith_instance(str2, 'abcs*abc', '666')
print(ns)
The output:
abc abc def 666
Answer #5:
How to replace the nth
needle
with word
:
s.replace(needle,'$$$',n-1).replace(needle,word,1).replace('$$$',needle)
Answer #6:
You can match the two occurrences of “cat”, keep everything before the second occurrence (1
) and add “Bull”:
new_str = re.sub(r'(cat.*?)cat', r'1Bull', str, 1)
We do only one substitution to avoid replacing the fourth, sixth, etc. occurrence of “cat” (when there are at least four occurrences), as pointed out by Avinash Raj comment.
If you want to replace the n
-th occurrence and not the second, use:
n = 2
new_str = re.sub('(cat.*?){%d}' % (n - 1) + 'cat', r'1Bull', str, 1)
BTW you should not use str
as a variable name since it is a Python reserved keyword.
Answer #7:
Create a repl function to pass into re.sub()
. Except… the trick is to make it a class so you can track the call count.
class ReplWrapper(object):
def __init__(self, replacement, occurrence):
self.count = 0
self.replacement = replacement
self.occurrence = occurrence
def repl(self, match):
self.count += 1
if self.occurrence == 0 or self.occurrence == self.count:
return match.expand(self.replacement)
else:
try:
return match.group(0)
except IndexError:
return match.group(0)
Then use it like this:
myrepl = ReplWrapper(r'Bull', 0) # replaces all instances in a string
new_str = re.sub(r'cat', myrepl.repl, str)
myrepl = ReplWrapper(r'Bull', 1) # replaces 1st instance in a string
new_str = re.sub(r'cat', myrepl.repl, str)
myrepl = ReplWrapper(r'Bull', 2) # replaces 2nd instance in a string
new_str = re.sub(r'cat', myrepl.repl, str)
I’m sure there is a more clever way to avoid using a class, but this seemed straight-forward enough to explain. Also, be sure to return match.expand()
as just returning the replacement value is not technically correct of someone decides to use 1
type templates.