Регулярные выражения (RegExp) — специальный язык для описания множества строк. Они помогают решать задачу поиска какого-либо текста (из описанного множества) в другом тексте, описывают интересующий нас текст и работают достаточно эффективно для быстрого решения задачи поиска.
В некоторых случаях количество вариантов искомого текста настолько велико, что перечислять все варианты становится неудобно. Иногда все эти варианты могут быть представлены одной строкой — регулярным выражением.
Примеры регулярных выражений (см. слайды):
-
KotlinAsFirst
-
@[A-Z0-9.-]\.[A-Z]{2,}
-
ˆ4[0-9]{12}(?:[0-9]{3})?$
-
[-]?[0-9]*\.?[0-9]
-
<()([ˆ<])(?:>(.)<\/\1>|\s+\/>)
Поиск регулярного выражения осуществляется с помощью автомата с состояниями, или конечного автомата. В данном случае под этим понимается алгоритм, имеющий некоторое количество устойчивых состояний. Для каждого состояния определяются действия, которые алгоритм выполняет в этом состоянии, а также условия, по которым алгоритм переходит в другие состояния.
Регулярное выражение в общем случае — это строка, в которой часть символов играет специальную роль. Но большинство символов в регулярном выражении обозначают просто самих себя. Например:
-
KotlinAsFirst
-
Трансмогрификация
-
Мама мыла раму
-
42
Существует однако ряд специальных символов. Например, класс символов обозначает любой символ из определённого множества:
-
— любая цифра[0123456789]
-
— любая буква из перечисленных[aeiouy]
-
— любой символ из перечисленных[~!@#$%^&*+-]
Отрицание класса символов ищет любой символ НЕ из заданного множества:
-
— всё, что угодно, кроме цифры[^0123456789]
-
— всё, что угодно, кроме строчной латинской буквы[^a-z]
-
— всё, что угодно, кроме[^-az]
-
,a
,z
Классы и их отрицания, как видим, используют специальные символы […]
для обозначения класса,
специальный символ -
для обозначения интервала символов и
последовательность [^…]
для обозначения отрицания.
Якоря позволяют найти начало или конец всей строки:
-
—^fun
fun
в начале строки -
— точка в конце строки\.$
-
— ВСЯ строка с заданной фразой (и более ничем)^Kotlin is great as the first language!$
Здесь ^
используется для обозначения начала строки, а $
для обозначения конца.
Следует иметь в виду, что якоря никак не учитывают переводы строк — имеется в виду начало или конец всего текста, а не одной строки в тексте.
\.
использует экранирование для обозначения символа .
,
поскольку в регулярных выражениях точка является специальным символом (и обозначает любой символ).
Таким образом, \
в регулярных выражениях экранирует последующий символ,
делая его из специального символа обыкновенным.
Для обозначения символа \
применяется пара \\
.
Аналогично, \^
обозначает символ-шапку, \$
— символ доллара, \[
— открывающую квадратную скобку,
\]
— закрывающую квадратную скобку.
Особые символы ищут символы по специальным правилам:
-
` …..
` — любая последовательность из пяти символов, начинающаяся и заканчивающаяся пробелов -
\t
— табуляция,\n
— новая строка,\r
— возврат каретки (два последних символа унаследованы компьютерами от эпохи пишущих машинок, когда для начала печати с новой строки необходимо было выполнить два действия — возврат каретки в начало строки и перевод каретки на новую строку) -
\s
— произвольный вид пробела (пробел, табуляция, новая строка, возврат каретки) -
\d
— произвольная цифра, аналог[0-9]
-
\w
— произвольная "символ в слове", обычно аналог[a-zA-z0-9]
, то есть, латинская буква или цифра -
\S
— НЕ пробел,\D
— НЕ цифра,\W
— НЕ "символ в слове"
Шаблон выбора |
ищет одну строку из нескольких, например:
-
— Марат или МихаилМарат|Михаил
-
— открывающая квадратная скобка в начале строки или закрывающая в конце^\[|\]$
-
— циклfor.(val|var).
for
с последующимval
илиvar
Шаблоны количества ищут определённое число совпадений:
-
— любое количество (в том числе ноль) любых символов.*
-
— строка Марат один или более раз (но не ноль)(Марат)+
-
— строка Михаил ноль или один раз(Михаил)?
-
— последовательность из ровно четырёх любых цифр([0-9]{4})
-
— последовательность из 8-16 "символов в слове"\w{8,16}
Круглые скобки ()
задают так называемые группы поиска, объединяя несколько символов вместе.
-
— KotlinAsFirst, KotlinKotlinAsFirst, KotlinKotlinKotlinAsFirst, …(Kotlin)+AsFirst
-
—(?:\$\$)+
`, ``, `
, … -
— слово, за которым следует пробел и то же самое слово.(\w+)\s\1
-
—fun\s+(/w+)\s*\{.\1.\}
fun
с последующими пробелами, произвольным словом в круглых скобках, пробелами и тем же словом в фигурных скобках
Здесь \1
(\2
, \3
, …) ищет уже описанную группу поиска по её номеру внутри регулярного выражения
(в данном случае — первую группу).
Комбинация (?:…)
задаёт группу поиска без номера.
В целом, (?…)
задаёт группы особого поиска:
-
— Марат, за которым следует пробел и АхинМарат(?=\sАхин)
-
— Глухих, перед которым стоит Михаил с пробелом(?⇐Михаил\s)Глухих
-
— число, после которого НЕ стоит знак доллара\d+(?![$\d])
-
— beer, перед которым НЕ стоит root с пробелом(?<!root\s)beer
Для описания регулярных выражений в Котлине используется тип Regex
.
Для создания регулярного выражения следует вызвать его конструктор, например Regex("KotlinAsFirst")
.
Второй способ создания регулярного выражения — вызов функции toRegex()
на строке-получателе,
например "KotlinAsFirst".toRegex()
.
При создании регулярных выражений вместо обычных строк в двойных кавычках рекомендуется использовать
так называемые raw string literals (необработанные строки).
Перед и после такого литерала должны стоять три двойных кавычки.
Внутри необработанных строк не применяется экранирование,
что позволяет применять специфичные для регулярных выражений символы без дополнительных ухищрений.
Например: Regex("""x|+|-|\*|/|\(|\)|\d+?| ?""")` --
задаёт выражение `x`, или `
, или -
, или …, или число, или любое количество пробелов.
Без тройных кавычек нам пришлось бы дважды записать каждый из \
.
Для анализа результата поиска применяется тип MatchResult
,
который можно получить, вызвав find
на регулярном выражении-получатале: Regex("""…""").find(string, startIndex)
.
find
ищет первое вхождение регулярного выражения в строку string
, начиная с индекса startIndex
(по умолчанию — 0).
Если вхождений регулярного выражения не найдено, результат find
равен null.
Regex("""…""").findAll(string, startIndex)
ищет ВСЕ вхождения регулярного выражения,
которые после этого можно перебрать с помощью цикла for
.
Тип MatchResult
включает в себя следующие свойства:
-
result.value
— подстрока исходной строки, с которой совпало регулярное выражение (совпадение) -
result.range
— интервал индексов символов, в котором было найдено совпадение -
result.groupValues
— список строк, 0-й элемент которого содержит всё регулярное выражение, а последующие содержат значения групп поиска из регулярного выражения (то есть размер списка равен числу групп поиска в выражении + 1)
Некоторые другие полезные методы, связанные:
-
Regex("""…""").replace("MyString", "Replacement") — находит в данной строке все вхождения регулярного выражения и заменяет их на `"Replacement"
-
"MyString".contains(Regex("""…"""))
— есть ли в данной строке хоть одно вхождение регулярного выражения -
Regex("""…""").containsMatchIn("MyString")
— то же самое, но в другом порядке -
"MyString".matches(Regex("""…"""))
— соответствует ли данная строка данному регулярному выражению -
Regex("""…""").matches("MyString")
— то же самое, но в другом порядке -
Regex("""…""").split("MyString")
— деление строки на части с использованием заданного регулярного выражения как разделителя
Мини-пример:
fun timeStrToSeconds(str: String): Int {
val matchResult = Regex("""(\d\d):(\d\d):(\d\d)""").find(str)
if (matchResult == null) return -1
return matchResult.groupValues.drop(1).map { it.toInt() }.fold(0) {
previous, next -> previous * 60 + next
}
}
Здесь мы разбираем исходную строку вида "12:34:56" с целью найти в ней три одинаковых группы поиска (\d\d)
.
Каждая из групп поиска включает в себя две цифры.
Убедившись с помощью проверки на null, что регулярное выражение успешно найдено,
мы отбрасываем первый элемент groupValues
с помощью функции drop(1)
,
оставляя, таким образом, в списке только значения трёх групп поиска.
Далее каждая из пар цифр конвертируется в число.
Результат сворачивается в число секунд, прошедших с начала дня, с помощью функции высшего порядка fold
— см. раздел 4.