TL; DR
re.search("(.)(?!.*\1)", text).group()
не соответствует первому не повторяющемуся символу, содержащемуся в тексте (он всегда возвращает символ в или перед первым не повторяющимся символом или до конца строки, если нет повторяющихся символов Я понимаю, что re.search() должен возвращать None, если совпадений не было).
Мне просто интересно понять, почему это регулярное выражение не работает по назначению с использованием модуля Python re
, а не в каком-либо другом методе решения проблемы.
Полный фон
Описание проблемы исходит из https://www.codeeval.com/open_challenges/12/. Я уже решил эту проблему с помощью метода non-regex, но переосмыслил его, чтобы расширить свое понимание модуля Python re
.
Регулярные выражения, которые, как я думал, будут работать (с именем vs unnamed backreferences):
(?P<letter>.)(?!.*(?P=letter))
и (.)(?!.*\1)
(то же самое получается в python2 и python3)
Вся моя программа выглядит так:
import re
import sys
with open(sys.argv[1], 'r') as test_cases:
for test in test_cases:
print(re.search("(?P<letter>.)(?!.*(?P=letter))",
test.strip()
).group()
)
и некоторые пары ввода/вывода:
rain | r
teetthing | e
cardiff | c
kangaroo | k
god | g
newtown | e
taxation | x
refurbished | f
substantially | u
В соответствии с тем, что я читал на https://docs.python.org/2/library/re.html:
-
(.)
создает именованную группу, которая соответствует любому символу и позволяет более поздние обратные ссылки на нее как\1
. -
(?!...)
- это отрицательный lookahead, который ограничивает совпадения с случаями, когда...
не соответствует. -
.*\1
означает любое число (включая ноль) символов, за которым следует все, что было сопоставлено(.)
ранее -
re.search(pattern, string)
возвращает только первое место, где шаблон регулярного выражения создает совпадение (и возвратит None, если совпадение не найдено) -
.group()
эквивалентен.group(0)
, который возвращает полное соответствие
Я думаю, что эти части вместе должны решить заявленную проблему, и она работает, как я думаю, она должна для большинства входов, но не сработала teething
. Выбрасывание подобных проблем в нем показывает, что, по-видимому, они игнорируют повторяющиеся символы, если они последовательны:
tooth | o # fails on consecutive repeated characters
aardvark | d # but does ok if it sees them later
aah | a # verified last one didn't work just because it was at start
heh | e # but it works for this one
hehe | h # What? It thinks h matches (lookahead maybe doesn't find "heh"?)
heho | e # but it definitely finds "heh" and stops "h" from matching here
hahah | a # so now it won't match h but will match a
hahxyz | a # but it realizes there are 2 h characters here...
hahxyza | h # ... Ok time for StackOverflow
Я знаю, что lookbehind и negative lookbehind ограничены строками фиксированной длины с 3 символами и не могут содержать обратные ссылки, даже если они оценивают строку фиксированной длины, но я не видел, чтобы в документации указывались какие-либо ограничения на негативный просмотр.