Граница слова со словами, начинающимися или заканчивающимися специальными символами, дает неожиданные результаты

#python #regex #string #match

#python #регулярное выражение

Вопрос:

Скажем, я хочу сопоставить наличие фразы Sortesindex[persons]{Sortes} во фразе test Sortesindex[persons]{Sortes} text .

Используя python re , я мог бы это сделать:

 >>> search = re.escape('Sortesindex[persons]{Sortes}')
>>> match = 'test Sortesindex[persons]{Sortes} text'
>>> re.search(search, match)
<_sre.SRE_Match object; span=(5, 34), match='Sortes\index[persons]{Sortes}'>
  

Это работает, но я хочу избежать шаблона поиска Sortes , чтобы дать положительный результат по фразе test Sortesindex[persons]{Sortes} text .

 >>> re.search(re.escape('Sortes'), match)
<_sre.SRE_Match object; span=(5, 11), match='Sortes'>
  

Поэтому я использую b шаблон, например:

 search = r'b'   re.escape('Sortesindex[persons]{Sortes}')   r'b'
match = 'test Sortesindex[persons]{Sortes} text'
re.search(search, match)
  

Теперь я не получаю совпадения.

Если шаблон поиска не содержит ни одного из символов []{} , он работает. Например.:

 >>> re.search(r'b'   re.escape('Sortesindex')   r'b', 'test Sortesindex test')
<_sre.SRE_Match object; span=(5, 17), match='Sortes\index'>
  

Кроме того, если я удалю финал r'b' , он также работает:

 re.search(r'b'   re.escape('Sortesindex[persons]{Sortes}'), 'test Sortesindex[persons]{Sortes} test')
<_sre.SRE_Match object; span=(5, 34), match='Sortes\index[persons]{Sortes}'>
  

Кроме того, в документации говорится о b

Обратите внимание, что формально b определяется как граница между символом w и символом W (или наоборот) или между w и началом / концом строки.

Поэтому я попытался заменить финал b на (W|$) :

 >>> re.search(r'b'   re.escape('Sortesindex[persons]{Sortes}')   '(W|$)', 'test Sortesindex[persons]{Sortes} test')
<_sre.SRE_Match object; span=(5, 35), match='Sortes\index[persons]{Sortes} '>
  

О чудо, это работает!
Что здесь происходит? Чего мне не хватает?

Комментарии:

1. } последний символ вашего шаблона не является словесным символом, как и пробел, следующий за ним. Таким образом, нет границы слова и нет совпадения. Если последний символ есть s , это символ слова, следовательно, существует граница слова.

Ответ №1:

Посмотрите, чему соответствует граница слова:

Граница слова может находиться в одной из трех позиций:

  • Перед первым символом в строке, если первый символ является символом слова.
  • После последнего символа в строке, если последний символ является символом слова.
  • Между двумя символами в строке, где один является символом слова, а другой не является символом слова.

В вашем шаблоне }b совпадают только в том случае, если после слова есть символ } (буква, цифра или _ ).

При использовании (W|$) вам явно требуется не слово или конец строки.

Решение — адаптивные границы слов:

 re.search(r'(?:(?!w)|b(?=w)){}(?:(?<=w)b|(?<!w))'.format(re.escape('Sortesindex[persons]{Sortes}')), 'test Sortesindex[persons]{Sortes} test')
  

Или эквивалент:

 re.search(r'(?!Bw){}(?<!wB)'.format(re.escape('Sortesindex[persons]{Sortes}')), 'test Sortesindex[persons]{Sortes} test')
  

Здесь используются адаптивные динамические границы слов, которые означают следующее:

  • (?:(?!w)|b(?=w)) (равно (?!Bw) ) — граница слева, обеспечивающая, что текущая позиция находится на границе слова, если следующий символ является символом слова, или ограничение контекста не применяется, если следующий символ не является символом слова (обратите внимание, что вам нужно будет использовать (?:B(?!w)|b(?=w)) , если вы хотите запретить словосимвол сразу слева, если следующий символ не является символом слова)
  • (?:(?<=w)b|(?<!w)) (равно (?<!wB) ) — граница справа, убедитесь, что текущая позиция находится на границе слова, если предыдущий символ является символом слова, или ограничение контекста не применяется, если предыдущий символ не является символом слова (обратите внимание, что вам нужно будет использовать (?:(?<=w)b|B(?<!w)) , если вы хотите запретить словосимвол сразу справа, если предыдущий символ не является символом слова).

В этих случаях вы также можете рассмотреть возможность использования однозначных границ слов на основе негативных поисковых запросов:

 re.search(r'(?<!w){}(?!w)'.format(re.escape('Sortesindex[persons]{Sortes}')), 'test Sortesindex[persons]{Sortes} test')
  

Здесь (?<!w) отрицательный поиск не приведет к совпадению, если слева от текущего местоположения есть слово char, а (?!w) отрицательный просмотр не приведет к совпадению, если справа от текущего местоположения есть слово char.

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

Примечание: эти шаблоны поиска легко настроить (скажем, чтобы не совпадать только при наличии букв вокруг шаблона, используйте [^Wd_] вместо w , или если вы разрешаете совпадения только вокруг пробелов, используйте границы пробелов / границы (?<!S) (?!S) поиска).

Комментарии:

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

2. @Stenskjaer b также является утверждением нулевой ширины, как и любой другой поиск. Поскольку эти шаблоны поиска содержат только отдельные атомы, накладные расходы не должны слишком отличаться от тех, которые у вас уже были с b s . Ну, вы можете установить быстрый тест производительности, если вы беспокоитесь, но это единственный правильный способ регулярного выражения, который я могу придумать для решения проблемы.

3. Верно! Я только что проверил это сам. Нет (обнаруживаемой) разницы в производительности. Спасибо.

4. Адаптивные границы слов могут быть записаны так: (?:(?!w)|b(?=w)) => (?!Bw) и (?:(?<=w)b|(?<!w)) => (?<!wB)

5. Кроме того, я записал видео YT «Динамические адаптивные границы слов» , где я подробно объясняю конструкции.

Ответ №2:

Я думаю, это то, с чем вы сталкиваетесь:

b попадает на границу w и W , но в примере это не работает. '{Sortes}b' является границей между W и W из '}' -за того, что не соответствует [a-zA-Z0-9_] обычному набору for w .