На этом шаге мы рассмотрим использование поименованных групп в регулярных выражениях.
Этот однострочник откроет для вас замечательную возможность регулярных выражений: повторное использование частей, для которых уже производился поиск
соответствий в том же регулярном выражении. Такое расширение возможностей дает возможность решать новый спектр задач, включая поиск строковых
значений с удвоенными символами.
На этот раз представьте, что вы как исследователь в сфере вычислительной лингвистики анализируете изменения частоты использования определенных слов с течением времени. Вы классифицируете слова и изучаете частоту их использования в различных напечатанных книгах. Ваш научный руководитель дает задание выяснить наличие тенденции к повышению частоты использования удвоенных символов в словах. Например, слово 'hello' содержит удвоенную букву 'l', а слово 'spoon' содержит удвоенную букву 'o'. Слово же 'mama' не относится к словам с удвоенной буквой 'a'.
Наивное решение этой задачи - перечислить все возможные варианты удвоения букв 'aa', 'bb', 'cc', 'dd', ... , 'zz' и объединить их в одном регулярном выражении. Это решение утомительно для реализации и плохо поддается обобщению. Что, если ваш научный руководитель передумает и попросит теперь искать удвоенные буквы, которые разделяет один символ (например, такие как в 'mama')?
Никаких проблем: существует простое, аккуратное и эффективное решение для тех, кто умеет обращаться с поименованными группами в регулярных выражениях. Вы уже встречали скобочные группы вида (...). Как ясно из названия, поименованная группа (named group) - это просто группа, у которой есть название. Например, описать поименованную группу для шаблона ... с названием name можно с помощью синтаксиса (?P<name>...). Описанную поименованную группу можно использовать где угодно в регулярном выражении, прибегнув к синтаксису (?P=name). Рассмотрим следующий пример:
import re pattern = '(?P<quote>[\'"]).*(?P=quote)' text = 'She said "hi"' print(re.search(pattern, text)) # <re.Match object; span=(9, 13), match='"hi"'>
В этом коде мы ищем подстроки, заключенные в одинарные или двойные кавычки. Для этого сначала ищем соответствие для открывающей кавычки с помощью регулярного выражения [\'"] (одинарную кавычку мы экранируем, чтобы Python ошибочно не счел ее символом окончания строкового значения). Далее с помощью той же группы ищем аналогичную закрывающую кавычку (одинарную или двойную).
Прежде чем заняться кодом, отметим, что можно искать соответствие произвольных пробельных символов с помощью регулярного выражения \s.
Можно также искать символы, не входящие во множество Y, посредством синтаксиса [^Y]. Вот и все, что нам нужно для решения поставленной задачи.
Рассмотрим задачу, изложенную в примере 5.8: найти в заданном тексте все слова с удвоенными символами. Под словом в данном случае понимается произвольная последовательность непробельных символов, отделенных произвольным количеством пробельных символов.
## Зависимости import re ## Данные text = ''' It was a bright cold day in April, and the clocks were striking thirteen. Winston Smith, his chin nuzzled into his breast in an effort to escape the vile wind, slipped quickly through the glass doors of Victory Mansions, though not quickly enough to prevent a swirl of gritty dust from entering along with him. -- George Orwell, 1984 ''' ## Однострочник duplicates = re.findall('([^\s]*(?P<x>[^\s])(?P=x)[^\s]*)', text) ## Результат print(duplicates)
Какие же слова с удвоенными символами найдет этот код?
Регулярное выражение (?P<x>[^\s]) задает новую группу с названием x, которая состоит из одного произвольного непробельного символа. Сразу за поименованной группой x следует регулярное выражение (?P=x), которому соответствует тот же символ, что и группе x. Мы нашли наши удвоенные символы! Однако задача состояла в поиске не просто удвоенных символов, а слов с удвоенными символами. Поэтому мы захватываем еще и произвольное число непробельных символов [^\s]* до и после удвоенного символа.
Стоит отметить, что это решение не вполне корректно обрабатывает слова, в которых встречается несколько удвоений символов. Оставим усовершенствование кода в качестве упражнения.
Результаты работы примера 5.8 выглядят следующим образом:
## Результаты print(duplicates) # [('thirteen.', 'e'), ('nuzzled', 'z'), ('effort', 'f'), ('slipped', 'p'), ('glass', 's'), ('doors', 'o'), ('gritty', 't'), ('--', '-'), ('Orwell,', 'l')]
Это регулярное выражение находит в тексте все слова с удвоенными символами. Отметим, что регулярное выражение из примера 5.8 включает две группы, так что каждый возвращаемый функцией re.findall() элемент состоит из кортежа, части которого соответствуют этим группам. Вы уже видели подобное поведение в предыдущих шагах.
На этом шаге вы добавили в свой набор инструментов регулярных выражений еще один замечательный инструмент: поименованные группы. В сочетании с двумя более незначительными возможностями поиска соответствий произвольным пробельным символам с помощью \s и описания набора символов, которые не должны встречаться на данном месте в шаблоне, с помощью оператора [^...] вы существенно приблизились к овладению регулярными выражениями Python в совершенстве.
На следующем шаге мы рассмотрим поиск повторов слов.