I have pulled the below snippet from question Replace nth occurrence of substring in string.
which will replace a single occurrence at the an nth substring. However I would like to replace all occurrences at every nth substring
so if there are 30 occurrences of a substring within the string, I would want to replace entires 10 and 20 for example, but im not sure how to achieve this at all
def nth_repl(s, sub, repl, nth):
find = s.find(sub)
# if find is not p1 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 != nth:
# find + 1 means we start at the last match start index + 1
find = s.find(sub, find + 1)
i += 1
# if i is equal to nth we found nth matches so replace
if i == nth:
return s[:find]+repl+s[find + len(sub):]
return s
The code you got from the previous question is a nice starting point, and only a minimal adaptation is required to have it change every nth occurence:
def nth_repl_all(s, sub, repl, nth):
find = s.find(sub)
# loop util we find no match
i = 1
while find != -1:
# if i is equal to nth we found nth matches so replace
if i == nth:
s = s[:find]+repl+s[find + len(sub):]
i = 0
# find + len(sub) + 1 means we start after the last match
find = s.find(sub, find + len(sub) + 1)
i += 1
return s
I would use re.sub
with a replacement function which keeps track of the matches, in an object to avoid using globals.
s = "hello world "*30
import re
class RepObj:
def __init__(self,replace_by,every):
self.__counter = 0
self.__every = every
self.__replace_by = replace_by
def doit(self,m):
rval = m.group(1) if self.__counter % self.__every else self.__replace_by
self.__counter += 1
return rval
r = RepObj("earth",5) # init replacement object with replacement and freq
result = re.sub("(world)",r.doit,s)
print(result)
result:
hello earth hello world hello world hello world hello world hello earth hello world hello world hello world hello world hello earth hello world hello world hello world hello world hello earth hello world hello world hello world hello world hello earth hello world hello world hello world hello world hello earth hello world hello world hello world hello world
EDIT: no need for an helper object, courtesy to Jon Clements (smart solutions as always), using a lambda
and a counter
to create a one-liner:
import re,itertools
s = "hello world "*30
result = re.sub('(world)', lambda m, c=itertools.count(): m.group() if next(c) % 5 else 'earth', s)
You can adapt the counter to suit your particular needs, and make it very complex, since the logic allows that.
One of the most efficient way to replace every nth substring is to split string by all substrings and then join by every nth.
This takes constant number of iterations over string:
def replace_nth(s, sub, repl, n=1):
chunks = s.split(sub)
size = len(chunks)
rows = size // n + (0 if size % n == 0 else 1)
return repl.join([
sub.join([chunks[i * n + j] for j in range(n if (i + 1) * n < size else size - i * n)])
for i in range(rows)
])
Example:
replace_nth('1 2 3 4 5 6 7 8 9 10', ' ', ',', 2)
>>> 1 2,3 4,5 6,7 8,9 10
replace_nth('1 2 3 4 5 6 7 8 9 10', ' ', '|', 3)
>>> 1 2 3|4 5 6|7 8 9|10
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With