替换字符串中第n次出现的子串

38
我想替换字符串中第 n 次出现的子字符串。
肯定有相当于我想做的事情的东西,它是这样的:mystring.replace("substring", 2nd) 最简单、最符合 Python 风格的方法是什么?
为什么不使用正则表达式:我不想使用正则表达式来解决这个问题,我找到的大多数类似问题的答案都只是用正则表达式剥离或者非常复杂的函数。我真的想要尽可能简单而不是正则表达式的解决方案。

回复:非重复问题:请阅读第一个答案之后的内容。 - TigerhawkT3
@TigerhawkT3 哦,我明白了。他要求只提供正则表达式的解决方案,但他也得到了非正则表达式的答案。我没有读过它们。你能编辑一下他的问题吗? - aleskva
OP想要非正则表达式的解决方案,而重复问题中有99%的基于正则表达式的解决方案。 - Padraic Cunningham
17个回答

27

如果存在,您可以使用while循环和str.find查找第n次出现并使用该位置创建新字符串:

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

示例:

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比允许的最大值(n = maximum_occurances + 1)大1时,@wjandre答案中存在一个简单的错误。例如,当: nth_repl('Blue headed racket tail', " ", "-", 4) 它将导致 'Blue headed racket tai-Blue headed racket tail'请查看我的答案:https://dev59.com/F1sW5IYBdhLWcg3wVWHj#68456906 - Haider
1
@Haider 这不是我的答案,而是 Padraic Cunningham 的。我只是编辑了它。 - wjandrea

12

我使用一个简单的函数,该函数列出所有出现的情况,选择第n个位置并将其用于将原始字符串拆分为两个子字符串。然后它会在第二个子字符串中替换第一个出现的情况,并将子字符串连接起来形成新的字符串:

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)

对于这些变量:

string = 'ababababababababab'
sub = 'ab'
wanted = 'CD'
n = 5

输出:

ababababCDabababab

注意:

where 变量实际上是一个匹配位置的列表,你可以选择第n个。但列表项索引通常从 0 开始,而不是从 1 开始。因此有一个 n-1 的索引,而 n 变量是实际的第n个子字符串。我的示例找到了第5个字符串。如果你使用 n 索引并想要查找第5个位置,则需要将 n 设置为 4。你通常使用哪一个取决于生成我们的 n 的函数。

这应该是最简单的方法,但可能不是最 Pythonic 的方法,因为 where 变量的构建需要导入 re 库。也许有人会发现更 Pythonic 的方法。

来源和一些链接如下:


如果指定的索引错误(例如太大),它会抛出一个错误,而不是什么都不做,这真的很烦人。 - ashrasmun
有时交互测试可以正常工作,但在我的代码中,split 会将“sub”中的第一个字符保留在“before”中。如果我将其编辑为“[n-1] - 1”,那么它就可以正确地分割了。这是怎么回事? - RichWalt

3

这可能是最简短的解决方案之一,而且没有使用任何外部库。

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

我做了几项测试,一切都完美无缺。


2
我已经想出了以下内容,还考虑了将所有“旧”字符串出现替换到左侧或右侧的选项。当然,没有替换所有出现的选项,因为标准的str.replace已经很完美了。
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
有点晚了,但我认为这种方法相当“Pythonic”(就我理解的意思而言),而且不需要使用for循环或计数器。
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

1

不符合Pythonic规范,也不高效,但可以用一行代码实现:

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)

如果您知道字符串中不存在某个“xxxxxx”占位符,您可以用占位符替换前n-1个出现的子串。然后将要查找的子串的第n个出现位置替换为该子串的第一个出现位置。最后将所有占位符替换回原始子串即可。

1
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

1
我对 @aleskva 的答案进行了微调,以更好地与正则表达式和通配符配合使用:
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

上一个答案几乎完美 - 只需要一个更正:

    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

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

@J.Warren - 你还应该为这种情况保护代码,即当string中的wanted字符串比你想要替换的sub出现次数少时。 - sophros

1

@Padraic Cunningham的答案中存在一个简单的错误,当出现次数n只比允许的次数(n = maximum_occurances + 1)多1时。

以下是他代码的修正版本:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接