Разные результаты регулярных выражений в онлайн песочнице и в re.findall в PyCharm
Дана задача:
Текстовый файл состоит из символов X, Y и Z. Определите максимальную длину цепочки вида XYZXYZXYZ... (составленной из фрагментов XYZ, последний фрагмент может быть неполным).
При написании шаблона возникло несоответствие: шаблон "(XYZ)+(XY|X)" в песочницах https://regexr.com/ и https://pythex.org/ работает как и ожидалось подсвечивая строки максимальной длины, в то время как findall в PyCharm выводит строки минимальной длины, например:
[('XYZ', 'XY'), ('XYZ', 'XY'), ('XYZ', 'X')]
Почему такое несоответствие и если я не понимаю логику findall, как поменять её чтобы findall искал и добавлял в список из пересекающихся подстрок подстроки максимальной длины
Ответы (1 шт):
Документация по re.findall
, ваш случай выделен:
The result depends on the number of capturing groups in the pattern. If there are no groups, return a list of strings matching the whole pattern. If there is exactly one group, return a list of strings matching that group. If multiple groups are present, return a list of tuples of strings matching the groups. Non-capturing groups do not affect the form of the result.
Перевод:
Результат зависит от количества захватывающих групп в образце. Если групп нет, возвращается список полных совпадений с образцом. Есть группа ровно одна, возвращается список совпадений с этой группой. Если групп несколько, возвращается список кортежей с совпадениями для всех групп. Не захватывающие группы не влияют на результат.
В вашем случае в образце есть две группы и вы получаете результаты парами. Первая группа повторяется метасимволом +
, она сопоставляется с несколькими фрагментами строки. В результат попадает первое совпадение. То есть в ответ попадёт пара ('XYZ', 'XY')
:
# это первый элемент пары из ответа # | # | это второй элемент # | | # v v # --- -- re.findall('(XYZ)+(XY|X)', 'XYZXYZXYZXYZXYZXY'))
Совпадение было полным, но в ответ помещены лишь его части, первая и последняя. Замените группы на не захватывающие, результатом станет полная строка, а не её фрагменты:
@>>> re.findall('(?:XYZ)+(?:XY|X)', 'XYZXYZXYZXYZXYZXY') ['XYZXYZXYZXYZXYZXY']
P.S. В комментариях уже написали что ваше выражение теряет последний символ Z
в совпадении. Поправить можно добавив знак вопроса в конце:
'(?:XYZ)+(?:XY|X)?'
.
P.P.S. Кроме того вы не захватываете совсем короткие строки X
и XY
. В тестах эта ошибка не будет видна. Но лучше не требовать обязательного вхождения первой группы:
'(?:XYZ)*(?:XY|X)?'
.
P.P.P.S. Причина недоразумения – сложная логика re.findall
. Посмотрите re.finditer
. Он делает ту же работу, расходует меньше памяти и вы получаете целиком совпадение, а не его кусочки. С ним '(XYZ)*(XY|X)?'
и '(?:XYZ)*(?:XY|X)?'
будут работать одинаково.