Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - replace every nth occurrence of string

Tags:

python

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
like image 536
AlexW Avatar asked Oct 12 '17 09:10

AlexW


Video Answer


3 Answers

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
like image 82
Serge Ballesta Avatar answered Nov 14 '22 22:11

Serge Ballesta


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.

like image 25
Jean-François Fabre Avatar answered Nov 14 '22 23:11

Jean-François Fabre


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
like image 34
Kam Lotfull Avatar answered Nov 14 '22 22:11

Kam Lotfull