Replace nth occurrence of substring in string
Question:
I want to replace the n’th occurrence of a substring in a string.
There’s got to be something equivalent to what I WANT to do which is
mystring.replace("substring", 2nd)
What is the simplest and most Pythonic way to achieve this?
Why not duplicate: I don’t want to use regex for this approach and most of answers to similar questions I found are just regex stripping or really complex function. I really want as simple as possible and not regex solution.
Answers:
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 = 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 with 0
usually, not with 1
. Therefore there is a n-1
index and n
variable is the actual nth substring. My example finds 5th string. If you use n
index and want to find 5th position, you’ll need n
to be 4
. Which you use usually depends on the function, which generates our n
.
This should be the simplest way, but maybe it isn’t the most Pythonic way, because the where
variable construction needs importing re
library. Maybe somebody will find even more Pythonic way.
Sources and some links in addition:
where
construction: How to find all occurrences of a substring?
- 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
You can use a while loop with str.find
to find the nth occurrence if it exists and use that position to create the new string:
def nth_repl(s, sub, repl, n):
find = s.find(sub)
# If find is not -1 we have found at least one match for the substring
i = find != -1
# loop util we find the nth or we find no match
while find != -1 and i != n:
# find + 1 means we start searching from after the last match
find = s.find(sub, find + 1)
i += 1
# If i is equal to n we found nth match so replace
if i == n:
return s[:find] + repl + s[find+len(sub):]
return s
Example:
In [14]: s = "foobarfoofoobarbar"
In [15]: nth_repl(s, "bar","replaced",3)
Out[15]: 'foobarfoofoobarreplaced'
In [16]: nth_repl(s, "foo","replaced",3)
Out[16]: 'foobarfooreplacedbarbar'
In [17]: nth_repl(s, "foo","replaced",5)
Out[17]: 'foobarfoofoobarbar'
The last answer is nearly perfect – only one correction:
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 = after.replace(sub, wanted)
newString = before + after
return newString
The after-string has to be stored in the this variable again after replacement.
Thank you for the great solution!
I have come up with the below, which considers also options to replace all ‘old’ string occurrences to the left or to the right. Naturally, there is no option to replace all occurrences, as standard str.replace works perfect.
def nth_replace(string, old, new, n=1, option='only nth'):
"""
This function replaces occurrences of string 'old' with string 'new'.
There are three types of replacement of string 'old':
1) 'only nth' replaces only nth occurrence (default).
2) 'all left' replaces nth occurrence and all occurrences to the left.
3) 'all right' replaces nth occurrence and all occurrences to the right.
"""
if option == 'only nth':
left_join = old
right_join = old
elif option == 'all left':
left_join = new
right_join = old
elif option == 'all right':
left_join = old
right_join = new
else:
print("Invalid option. Please choose from: 'only nth' (default), 'all left' or 'all right'")
return None
groups = string.split(old)
nth_split = [left_join.join(groups[:n]), right_join.join(groups[n:])]
return new.join(nth_split)
I had a similar need, i.e to find the IPs in logs and replace only src IP or dst IP field selectively. This is how i achieved in a pythonic way;
import re
mystr = '203.23.48.0 DENIED 302 449 800 1.1 302 http d.flashresultats.fr 10.111.103.202 GET GET - 188.92.40.78 '
src = '1.1.1.1'
replace_nth = lambda mystr, pattern, sub, n: re.sub(re.findall(pattern, mystr)[n - 1], sub, mystr)
result = replace_nth(mystr, 'S*d+.d+.d+.d+S*', src, 2)
print(result)
def replace_nth_occurance(some_str, original, replacement, n):
""" Replace nth occurance of a string with another string
"""
all_replaced = some_str.replace(original, replacement, n) # Replace all originals up to (including) nth occurance and assign it to the variable.
for i in range(n):
first_originals_back = all_replaced.replace(replacement, original, i) # Restore originals up to nth occurance (not including nth)
return first_originals_back
I’ve tweaked @aleskva’s answer to better work with regex and wildcards:
import re
def replacenth(string, sub, wanted, n):
pattern = re.compile(sub)
where = [m for m in pattern.finditer(string)][n-1]
before = string[:where.start()]
after = string[where.end():]
newString = before + wanted + after
return newString
replacenth('abdsahd124njhdasjk124ndjaksnd124ndjkas', '1.*?n', '15', 1)
This gives abdsahd15jhdasjk124ndjaksnd124ndjkas
. Note the use of ?
to make the query non-greedy.
I realise that the question explicitly states that they didn’t want to use regex, however it may be useful to be able to use wildcards in a clear fashion (hence my answer).
General solution: replace any specified instance(s) of a substring [pattern] with another string.
def replace(instring,pattern,replacement,n=[1]):
"""Replace specified instance(s) of pattern in string.
Positional arguments
instring - input string
pattern - regular expression pattern to search for
replacement - replacement
Keyword arguments
n - list of instances requested to be replaced [default [1]]
"""
import re
outstring=''
i=0
for j,m in enumerate(re.finditer(pattern,instring)):
if j+1 in n: outstring+=instring[i:m.start()]+replacement
else: outstring+=instring[i:m.end()]
i=m.end()
outstring+=instring[i:]
return outstring
My two cents
a='01ab12ab23ab34ab45ab56ab67ab78ab89ab90';print('The original string: ', a)
sTar = 'ab';print('Look for: ', sTar)
n = 4; print('At occurence #:', n)
sSub = '***';print('Substitute with: ', sSub)
t = 0
for i in range(n):
t = a.find(sTar,t)
print(i+1, 'x occurence at', t)
if t != -1: t+=1
t-=1 #reset, get the correct location
yy = a[:t] + a[t:].replace(sTar, sSub, 1)
print('New string is:', yy)
Output
The original string: 01ab12ab23ab34ab45ab56ab67ab78ab89ab90
Look for: ab
At occurence #: 4
Substitute with: ***
1 x occurence at 2
2 x occurence at 6
3 x occurence at 10
4 x occurence at 14
New string is: 01ab12ab23ab34***45ab56ab67ab78ab89ab90
Elegant and short:
def replace_ocurrance(string,from,to,num)
strange_char = “$&$@$$&”
return string.replace(from,strange_char,num).replace(strange_char, from,num-1).replace(to, strange_char,1)
There’s only a couple of non-Regex answers and wanted to provide my own solution which I think is simpler and easier to understand. Create a new string and count for the nth occurence of the character you want to replace.
def replace_nth_occurence(old_str, old_char, new_char, n):
new_str = ""
occurences = 0
for s in old_str:
if s == old_char:
occurences += 1
if occurences == n:
new_str += new_char # append the new character instead of the old
else:
new_str += s
else:
new_str += s
return new_str
replace_nth_occurence("Testing_One_Two_Three", "_", "?", 3)
>> Testing_One_Two?Three
Bit late to the party, but I would consider this way quite pythonian (as far as I understand the meaning of that) and it doesn’t require a for loop or counter
def Nreplacer(string,srch,rplc,n):
Sstring = string.split(srch)
#first check if substring is even present n times
#then paste the part before the nth substring to the part after the nth substring
#, with the replacement inbetween
if len(Sstring) > (n):
return f'{srch.join(Sstring[:(n)])}{rplc}{srch.join(Sstring[n:])}'
else:
return string
Probably one of the shortest solutions and simplest on here without any external library.
def replace_nth(sub,repl,txt,nth):
arr=txt.split(sub)
part1=sub.join(arr[:nth])
part2=sub.join(arr[nth:])
return part1+repl+part2
I did a couple of tests and it worked perfectly.
There is a simple bug in @Padraic Cunningham‘s answer when the occurrence n is only larger by only 1 than what allowed (n = maximum_occurances + 1).
So here is a corrected version of his code:
def nth_repl(s, old, new, n):
find = s.find(old)
# If find is not -1 we have found at least one match for the substring
i = find != -1
# loop until we find the nth or we find no match
while find != -1 and i != n:
# find + 1 means we start searching from after the last match
find = s.find(old, find + 1)
i += 1
# If i is equal to n we found nth match so replace
if i == n and i <= len(s.split(old))-1:
return s[:find] + new + s[find+len(old):]
return s
Not pythonic and not efficient, but a one-liner is:
def replace_nth(base_str, find_str, replace_str, n):
return base_str.replace(find_str, "xxxxx", n-1).replace(find_str, replace_str, 1).replace("xxxxx", find_str)
If you know that some "xxxxxx" placeholder doesn’t exist in the string, you can replace the n-1 first ocurrences by a placeholder. Then replace the n-th ocurrence of the substring you’re looking for, which at this point is the first ocurrence. Then replace all the placeholders back to the original substring.
I have a one-liner
if your regex is reg
and you need to replace it with reg2
:
"".join([reg + x if i != INDEX else reg2 + x for i, x in enumerate(YOUR_STRING.split(reg))])[len(reg):]
I want to replace the n’th occurrence of a substring in a string.
There’s got to be something equivalent to what I WANT to do which is
mystring.replace("substring", 2nd)
What is the simplest and most Pythonic way to achieve this?
Why not duplicate: I don’t want to use regex for this approach and most of answers to similar questions I found are just regex stripping or really complex function. I really want as simple as possible and not regex solution.
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 = 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 maybe it isn’t the most Pythonic way, because the
where
variable construction needs importingre
library. Maybe somebody will find even more Pythonic way.
Sources and some links in addition:
where
construction: How to find all occurrences of a substring?- 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
You can use a while loop with str.find
to find the nth occurrence if it exists and use that position to create the new string:
def nth_repl(s, sub, repl, n):
find = s.find(sub)
# If find is not -1 we have found at least one match for the substring
i = find != -1
# loop util we find the nth or we find no match
while find != -1 and i != n:
# find + 1 means we start searching from after the last match
find = s.find(sub, find + 1)
i += 1
# If i is equal to n we found nth match so replace
if i == n:
return s[:find] + repl + s[find+len(sub):]
return s
Example:
In [14]: s = "foobarfoofoobarbar"
In [15]: nth_repl(s, "bar","replaced",3)
Out[15]: 'foobarfoofoobarreplaced'
In [16]: nth_repl(s, "foo","replaced",3)
Out[16]: 'foobarfooreplacedbarbar'
In [17]: nth_repl(s, "foo","replaced",5)
Out[17]: 'foobarfoofoobarbar'
The last answer is nearly perfect – only one correction:
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 = after.replace(sub, wanted)
newString = before + after
return newString
The after-string has to be stored in the this variable again after replacement.
Thank you for the great solution!
I have come up with the below, which considers also options to replace all ‘old’ string occurrences to the left or to the right. Naturally, there is no option to replace all occurrences, as standard str.replace works perfect.
def nth_replace(string, old, new, n=1, option='only nth'):
"""
This function replaces occurrences of string 'old' with string 'new'.
There are three types of replacement of string 'old':
1) 'only nth' replaces only nth occurrence (default).
2) 'all left' replaces nth occurrence and all occurrences to the left.
3) 'all right' replaces nth occurrence and all occurrences to the right.
"""
if option == 'only nth':
left_join = old
right_join = old
elif option == 'all left':
left_join = new
right_join = old
elif option == 'all right':
left_join = old
right_join = new
else:
print("Invalid option. Please choose from: 'only nth' (default), 'all left' or 'all right'")
return None
groups = string.split(old)
nth_split = [left_join.join(groups[:n]), right_join.join(groups[n:])]
return new.join(nth_split)
I had a similar need, i.e to find the IPs in logs and replace only src IP or dst IP field selectively. This is how i achieved in a pythonic way;
import re
mystr = '203.23.48.0 DENIED 302 449 800 1.1 302 http d.flashresultats.fr 10.111.103.202 GET GET - 188.92.40.78 '
src = '1.1.1.1'
replace_nth = lambda mystr, pattern, sub, n: re.sub(re.findall(pattern, mystr)[n - 1], sub, mystr)
result = replace_nth(mystr, 'S*d+.d+.d+.d+S*', src, 2)
print(result)
def replace_nth_occurance(some_str, original, replacement, n):
""" Replace nth occurance of a string with another string
"""
all_replaced = some_str.replace(original, replacement, n) # Replace all originals up to (including) nth occurance and assign it to the variable.
for i in range(n):
first_originals_back = all_replaced.replace(replacement, original, i) # Restore originals up to nth occurance (not including nth)
return first_originals_back
I’ve tweaked @aleskva’s answer to better work with regex and wildcards:
import re
def replacenth(string, sub, wanted, n):
pattern = re.compile(sub)
where = [m for m in pattern.finditer(string)][n-1]
before = string[:where.start()]
after = string[where.end():]
newString = before + wanted + after
return newString
replacenth('abdsahd124njhdasjk124ndjaksnd124ndjkas', '1.*?n', '15', 1)
This gives abdsahd15jhdasjk124ndjaksnd124ndjkas
. Note the use of ?
to make the query non-greedy.
I realise that the question explicitly states that they didn’t want to use regex, however it may be useful to be able to use wildcards in a clear fashion (hence my answer).
General solution: replace any specified instance(s) of a substring [pattern] with another string.
def replace(instring,pattern,replacement,n=[1]):
"""Replace specified instance(s) of pattern in string.
Positional arguments
instring - input string
pattern - regular expression pattern to search for
replacement - replacement
Keyword arguments
n - list of instances requested to be replaced [default [1]]
"""
import re
outstring=''
i=0
for j,m in enumerate(re.finditer(pattern,instring)):
if j+1 in n: outstring+=instring[i:m.start()]+replacement
else: outstring+=instring[i:m.end()]
i=m.end()
outstring+=instring[i:]
return outstring
My two cents
a='01ab12ab23ab34ab45ab56ab67ab78ab89ab90';print('The original string: ', a)
sTar = 'ab';print('Look for: ', sTar)
n = 4; print('At occurence #:', n)
sSub = '***';print('Substitute with: ', sSub)
t = 0
for i in range(n):
t = a.find(sTar,t)
print(i+1, 'x occurence at', t)
if t != -1: t+=1
t-=1 #reset, get the correct location
yy = a[:t] + a[t:].replace(sTar, sSub, 1)
print('New string is:', yy)
Output
The original string: 01ab12ab23ab34ab45ab56ab67ab78ab89ab90
Look for: ab
At occurence #: 4
Substitute with: ***
1 x occurence at 2
2 x occurence at 6
3 x occurence at 10
4 x occurence at 14
New string is: 01ab12ab23ab34***45ab56ab67ab78ab89ab90
Elegant and short:
def replace_ocurrance(string,from,to,num)
strange_char = “$&$@$$&”
return string.replace(from,strange_char,num).replace(strange_char, from,num-1).replace(to, strange_char,1)
There’s only a couple of non-Regex answers and wanted to provide my own solution which I think is simpler and easier to understand. Create a new string and count for the nth occurence of the character you want to replace.
def replace_nth_occurence(old_str, old_char, new_char, n):
new_str = ""
occurences = 0
for s in old_str:
if s == old_char:
occurences += 1
if occurences == n:
new_str += new_char # append the new character instead of the old
else:
new_str += s
else:
new_str += s
return new_str
replace_nth_occurence("Testing_One_Two_Three", "_", "?", 3)
>> Testing_One_Two?Three
Bit late to the party, but I would consider this way quite pythonian (as far as I understand the meaning of that) and it doesn’t require a for loop or counter
def Nreplacer(string,srch,rplc,n):
Sstring = string.split(srch)
#first check if substring is even present n times
#then paste the part before the nth substring to the part after the nth substring
#, with the replacement inbetween
if len(Sstring) > (n):
return f'{srch.join(Sstring[:(n)])}{rplc}{srch.join(Sstring[n:])}'
else:
return string
Probably one of the shortest solutions and simplest on here without any external library.
def replace_nth(sub,repl,txt,nth):
arr=txt.split(sub)
part1=sub.join(arr[:nth])
part2=sub.join(arr[nth:])
return part1+repl+part2
I did a couple of tests and it worked perfectly.
There is a simple bug in @Padraic Cunningham‘s answer when the occurrence n is only larger by only 1 than what allowed (n = maximum_occurances + 1).
So here is a corrected version of his code:
def nth_repl(s, old, new, n):
find = s.find(old)
# If find is not -1 we have found at least one match for the substring
i = find != -1
# loop until we find the nth or we find no match
while find != -1 and i != n:
# find + 1 means we start searching from after the last match
find = s.find(old, find + 1)
i += 1
# If i is equal to n we found nth match so replace
if i == n and i <= len(s.split(old))-1:
return s[:find] + new + s[find+len(old):]
return s
Not pythonic and not efficient, but a one-liner is:
def replace_nth(base_str, find_str, replace_str, n):
return base_str.replace(find_str, "xxxxx", n-1).replace(find_str, replace_str, 1).replace("xxxxx", find_str)
If you know that some "xxxxxx" placeholder doesn’t exist in the string, you can replace the n-1 first ocurrences by a placeholder. Then replace the n-th ocurrence of the substring you’re looking for, which at this point is the first ocurrence. Then replace all the placeholders back to the original substring.
I have a one-liner
if your regex is reg
and you need to replace it with reg2
:
"".join([reg + x if i != INDEX else reg2 + x for i, x in enumerate(YOUR_STRING.split(reg))])[len(reg):]