Как найти и заменить 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)