关于python:替换字符串中子字符串的第n个出现

Replace nth occurrence of substring in string

我想替换字符串中第n个出现的子字符串。

一定有什么我想做的事情是

mystring.replace("substring", 2nd)

实现这一目标的最简单,最Python化的方法是什么?

为什么不重复:我不想在这种方法中使用正则表达式,而我发现的类似问题的大多数答案都只是正则表达式剥离或真正复杂的功能。 我真的想要尽可能简单而不是正则表达式的解决方案。


您可以使用带有str.find的while循环来查找第n个匹配项(如果存在),并使用该位置来创建新字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

例:

1
2
3
4
5
6
7
8
9
10
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'

我使用简单的函数,该函数列出所有出现的事件,选择第n个位置,并使用它将原始字符串分成两个子字符串。然后,它替换第二个子字符串中的第一个匹配项,并将子字符串连接回新的字符串中:

1
2
3
4
5
6
7
8
9
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

对于这些变量:

1
2
3
4
string = 'ababababababababab'
sub = 'ab'
wanted = 'CD'
n = 5

输出:

1
ababababCDabababab

笔记:

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: 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


我提出了以下内容,其中还考虑了替换左侧或右侧所有"旧"字符串出现的选项。自然,没有替代所有出现的选项,因为标准的str.replace可以完美地工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)

最后一个答案几乎是完美的-只有一个更正:

1
2
3
4
5
6
7
    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

替换后,后字符串必须再次存储在this变量中。
谢谢您的出色解决方案!


我调整了@aleskva的答案,以更好地使用正则表达式和通配符:

1
2
3
4
5
6
7
8
9
10
11
12
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)

这给出abdsahd15jhdasjk124ndjaksnd124ndjkas。请注意,使用?可使查询变为非贪婪。

我意识到这个问题明确指出他们不想使用正则表达式,但是能够以清晰的方式使用通配符可能会很有用(因此,我的回答是)。


1
2
3
4
5
6
7
def replace_nth_occurance(some_str, original, replacement, n):
   """ Replace nth occurance of a string with another string
   """

    some_str.replace(original, replacement, n)
    for i in range(n):
        some_str.replace(replacement, original, i)
    return some_str


我有类似的需求,即在日志中查找IP并选择性地仅替换src IP或dst IP字段。这就是我以Python方式实现的方式;

1
2
3
4
5
6
7
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)