Как найти и заменить n-е вхождение слова в предложении, используя регулярное выражение python?

Используя только регулярное выражение python, как найти и заменить n-е вхождение слова в предложении? Например:

str = 'cat goose  mouse horse pig cat cow'
new_str = re.sub(r'cat', r'Bull', str)
new_str = re.sub(r'cat', r'Bull', str, 1)
new_str = re.sub(r'cat', r'Bull', str, 2)

У меня есть предложение выше, где слово "кошка" встречается в предложении два раза. Я хочу, чтобы второе вхождение слова "кошка" было изменено на "бык", оставив слово "кошка" без изменений. Мое последнее предложение будет выглядеть так: "кошка гусиная мышь лошадь pig бык корова". В моем коде выше я пробовал 3 разных раза, не мог получить то, что хотел.

Ответ 1

Используйте негативный вид, как показано ниже.

>>> s = "cat goose  mouse horse pig cat cow"
>>> re.sub(r'^((?:(?!cat).)*cat(?:(?!cat).)*)cat', r'\1Bull', s)
'cat goose  mouse horse pig Bull cow'

DEMO

  • ^ Утверждается, что мы в самом начале.
  • (?:(?!cat).)* Совпадает с любым персонажем, но не с cat, ноль или более раз.
  • cat соответствует первой подстроке cat.
  • (?:(?!cat).)* Совпадает с любым персонажем, но не с cat, ноль или более раз.
  • Теперь включите все шаблоны внутри группы захвата, такие как ((?:(?!cat).)*cat(?:(?!cat).)*), Чтобы мы могли позже ссылаться на эти захваченные символы.
  • cat теперь выполняется следующая вторая строка cat.

ИЛИ ЖЕ

>>> s = "cat goose  mouse horse pig cat cow"
>>> re.sub(r'^(.*?(cat.*?){1})cat', r'\1Bull', s)
'cat goose  mouse horse pig Bull cow'

Измените номер внутри {} чтобы заменить первое или второе или n-е вхождение строки cat

Чтобы заменить третье вхождение строки cat, поместите 2 внутри фигурных скобок.

>>> re.sub(r'^(.*?(cat.*?){2})cat', r'\1Bull', "cat goose  mouse horse pig cat foo cat cow")
'cat goose  mouse horse pig cat foo Bull cow'

Играйте с вышеуказанным регулярным выражением здесь...

Ответ 2

Здесь можно сделать это без регулярного выражения:

def replaceNth(s, source, target, n):
    inds = [i for i in range(len(s) - len(source)+1) if s[i:i+len(source)]==source]
    if len(inds) < n:
        return  # or maybe raise an error
    s = list(s)  # can't assign to string slices. So, let listify
    s[inds[n-1]:inds[n-1]+len(source)] = target  # do n-1 because we start from the first occurrence of the string, not the 0-th
    return ''.join(s)

Использование:

In [278]: s
Out[278]: 'cat goose  mouse horse pig cat cow'

In [279]: replaceNth(s, 'cat', 'Bull', 2)
Out[279]: 'cat goose  mouse horse pig Bull cow'

In [280]: print(replaceNth(s, 'cat', 'Bull', 3))
None

Ответ 3

Я использую простую функцию, которая перечисляет все вхождения, выбирает 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.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.

Это должен быть самый простой способ, но он не является регулярным выражением только по своему желанию.

Источники и некоторые ссылки дополнительно:

Ответ 4

Я бы определил функцию, которая будет работать для каждого регулярного выражения:

import re

def replace_ith_instance(string, pattern, new_str, i = None, pattern_flags = 0):
    # If i is None - replacing last occurrence
    match_obj = re.finditer(r'{0}'.format(pattern), string, flags = pattern_flags)
    matches = [item for item in match_obj]
    if i == None:
        i = len(matches)
    if len(matches) == 0 or len(matches) < i:
        return string
    match = matches[i - 1]
    match_start_index = match.start()
    match_len = len(match.group())

    return '{0}{1}{2}'.format(string[0:match_start_index], new_str, string[match_start_index + match_len:])

Рабочий пример:

str = 'cat goose  mouse horse pig cat cow'
ns = replace_ith_instance(str, 'cat', 'Bull', 2)
print(ns)

Выход:

cat goose  mouse horse pig Bull cow

Другой пример:

str2 = 'abc abc def abc abc'
ns = replace_ith_instance(str2, 'abc\s*abc', '666')
print(ns)

Выход:

abc abc def 666

Ответ 5

Вы можете сопоставить два вхождения "cat", сохранить все до второго появления (\1) и добавить "Bull":

new_str = re.sub(r'(cat.*?)cat', r'\1Bull', str, 1)

Мы выполняем только одну замену, чтобы избежать замены четвертого, шестого и т.д. "кошки" (когда есть не менее четырех случаев), как указано в комментарии Avinash Raj.

Если вы хотите заменить n -ное вхождение, а не второе, используйте:

n = 2
new_str = re.sub('(cat.*?){%d}' % (n - 1) + 'cat', r'\1Bull', str, 1)

Кстати, вы не должны использовать str как имя переменной, так как это ключевое слово с зарезервированным Python.

Ответ 6

Создайте функцию repl, чтобы перейти в re.sub(). Кроме того, трюк состоит в том, чтобы сделать его классом, чтобы вы могли отслеживать количество вызовов.

class ReplWrapper(object):
    def __init__(self, replacement, occurrence):
        self.count = 0
        self.replacement = replacement
        self.occurrence = occurrence
    def repl(self, match):
        self.count += 1
        if self.occurrence == 0 or self.occurrence == self.count:
            return match.expand(self.replacement)
        else:
            try:
                return match.group(0)
            except IndexError:
                return match.group(0)

Затем используйте его следующим образом:

myrepl = ReplWrapper(r'Bull', 0) # replaces all instances in a string
new_str = re.sub(r'cat', myrepl.repl, str)

myrepl = ReplWrapper(r'Bull', 1) # replaces 1st instance in a string
new_str = re.sub(r'cat', myrepl.repl, str)

myrepl = ReplWrapper(r'Bull', 2) # replaces 2nd instance in a string
new_str = re.sub(r'cat', myrepl.repl, str)

Я уверен, что есть более умный способ избежать использования класса, но это казалось достаточно прямым, чтобы объяснить. Кроме того, обязательно верните match.expand(), так как только возврат значения замены не является технически правильным, если кто-то решит использовать шаблоны типов \1.

Ответ 7

Как заменить nth needle word:

s.replace(needle,'$$$',n-1).replace(needle,word,1).replace('$$$',needle)