Своеобразная работа '*' в выражении регулярного выражения

При написании шаблона регулярных выражений для замены всех непрерывных "1" и одиночных "1" как "s". Я нашел это довольно запутанным, использование "+" (используется для сопоставления 1 или более) дал ожидаемый результат, но "*" дал странный результат

>>> l='100'
>>> import re
>>> j=re.compile(r'(1)*')    
>>> m=j.sub('*',l)
>>> m
'*0*0*'

В то время как использование "+" дало ожидаемый результат.

>>> l='100'
>>> j=re.compile(r'1+')
>>> m=j.sub('*',l)
>>> m
'*00'

как '*' в регулярном выражении дает это, в то время как его поведение должно соответствовать 0 или больше.

3 ответа

Решение

(1)* означает "соответствовать 0 или более 1". Таким образом, для 100 оно соответствует 1, пустой строке между 0 и 0 и пустой строке после последней 0. Затем вы заменяете пустые строки на "*". 1+ требует как минимум одну 1 в совпадении, поэтому она не будет соответствовать границе между символами.

Для тех читателей, любопытно, да выход Python *0*0* и не **0*0*, Вот тестовый скрипт на python для игры. (Regex101 имеет неправильный вывод для этого, потому что он не использует реальный движок регулярных выражений Python. Онлайн-тестировщики регулярных выражений обычно используют PCRE (который предоставляется в PHP и HTTP-сервере Apache) и подделывают целевой движок регулярных выражений. Всегда проверяйте свое регулярное выражение в живом коде!)

Здесь вы можете увидеть в JavaScript вывод будет **0*0* (оно будет соответствовать пустой строке между 1 и 0 как новое совпадение) Это яркий пример того, почему так важно 'regex flavour'. Различные двигатели используют немного разные правила. (в этом случае, если новое совпадение начинается с 0 или границы символа)

console.log("100".replace(/(1)*/g, '*'))

Остерегайтесь шаблонов, которые не могут сравниться ни с чем. Это не четко определено, поэтому поведение зависит от двигателя. Например, вы получаете другой результат в Perl.

$ perl -e'CORE::say "100" =~ s/1*/\*/rg'
**0*0*
  1. В позиции 0 он соответствует 1 символу.
  2. В позиции 1 он соответствует 0 символам.
  3. [Вынужден продвигаться, чтобы избежать бесконечного цикла]
  4. В позиции 2 он соответствует 0 символам.
  5. [Вынужден продвигаться, чтобы избежать бесконечного цикла]
  6. В позиции 3 он соответствует 0 символам.
  7. [Вынужден продвигаться, чтобы избежать бесконечного цикла]
  8. [Не соответствует: за пределами строки]
regex = r"1*"
p = re.compile(regex)
test_str = "100"
for m in p.finditer(test_str):
    print(m.start(), m.group())

Выводит 4 совпадения (поэтому regex101 показывает 4 совпадения):

0 1
1 
2 
3 

В то время как re.sub() заменяет 3 позиции, что является причиной re.sub() продвижение после совпадения нулевой длины ( Python doc):

sub (pattern, repl, string, count = 0, flags = 0)

Вернуть строку, полученную путем замены самого левого неперекрывающегося вхождения шаблона в строке заменой repl.

...

Пустые совпадения для шаблона заменяются только тогда, когда они не соседствуют с предыдущим совпадением, поэтому sub('x*', '-', 'abc') возвращается '-a-b-c-',

Что означает неперекрывающееся вхождение? Это означает, когда:

первый матч закончился в начале строки, где началась первая попытка матча. Механизм регулярных выражений нуждается в способе избежать застревания в бесконечном цикле, который всегда находит одинаковое совпадение нулевой длины в начале строки.

Самое простое решение, которое используется большинством механизмов регулярных выражений, состоит в том, чтобы начать следующую попытку сопоставления через один символ после окончания предыдущего сопоставления, если предыдущее совпадение было нулевой длины.

В этом случае вторая попытка сопоставления начинается с позиции между 1 и 0 в строке, отсюда и разница.

Другие вопросы по тегам