Простой сопоставитель регулярных выражений — различия между версиями
Строка 20: | Строка 20: | ||
'''function''' match(regexp: '''String''', text: '''String'''): '''boolean''' | '''function''' match(regexp: '''String''', text: '''String'''): '''boolean''' | ||
'''if''' regexp[0] == '^' | '''if''' regexp[0] == '^' | ||
− | '''return''' matchHere(regexp | + | '''return''' matchHere(regexp[1:], text) <font color=darkgreen>// regexp[n:] возвращает regexp без первых n элементов за O(1)</font> |
'''int''' i = 0 | '''int''' i = 0 | ||
'''while''' i <tex>\leqslant</tex> text.length | '''while''' i <tex>\leqslant</tex> text.length | ||
− | '''if''' matchHere(regexp, text | + | '''if''' matchHere(regexp, text[1:]) |
'''return''' ''true'' | '''return''' ''true'' | ||
i++ | i++ | ||
Строка 37: | Строка 37: | ||
'''function''' matchHere(regexp: '''String''', text: '''String'''): '''boolean''' | '''function''' matchHere(regexp: '''String''', text: '''String'''): '''boolean''' | ||
'''if''' regexp[0] == '\0' | '''if''' regexp[0] == '\0' | ||
− | '''return''' ''true'' | + | '''return''' ''true'' |
− | '''if''' regexp[1] == '*' | + | '''if''' regexp[1] == '*' <font color=darkgreen>// не будет выхода за пределы строки, так как в конце regexp и text всегда есть символ '\0'</font> |
− | '''return''' matchStar(regexp[0], regexp | + | '''return''' matchStar(regexp[0], regexp[2:], text) |
'''if''' regexp[0] == '$' '''and''' regexp[1] == '\0' | '''if''' regexp[0] == '$' '''and''' regexp[1] == '\0' | ||
− | '''return''' text == '\0' | + | '''return''' text == '\0' |
'''if''' text[0] != '\0' '''and''' (regexp[0] == '.' '''or''' regexp[0] == text[0]) | '''if''' text[0] != '\0' '''and''' (regexp[0] == '.' '''or''' regexp[0] == text[0]) | ||
− | '''return''' matchHere(regexp | + | '''return''' matchHere(regexp[1:], text[1:]) |
'''return''' ''false'' | '''return''' ''false'' | ||
Строка 52: | Строка 52: | ||
'''int''' i = 0 | '''int''' i = 0 | ||
'''while''' i <tex>\leqslant</tex> text.length '''and''' (text[i] == c '''or''' c == '.') | '''while''' i <tex>\leqslant</tex> text.length '''and''' (text[i] == c '''or''' c == '.') | ||
− | '''if''' matchHere(regexp, text | + | '''if''' matchHere(regexp, text[i:]) |
'''return''' ''true'' | '''return''' ''true'' | ||
i++ | i++ | ||
Строка 58: | Строка 58: | ||
Рассмотрим возможные случаи: | Рассмотрим возможные случаи: | ||
− | # Если в ходе рекурсии регулярное выражение осталось пустым <tex>(regexp[0] == \backslash0)</tex>, то текст допускается этим регулярным выражением. | + | # Если в ходе рекурсии регулярное выражение осталось пустым <tex>\mathrm{(regexp[0] == \backslash0)}</tex>, то текст допускается этим регулярным выражением. |
− | # Если регулярное выражение имеет вид <tex>c*</tex>, то вызывается функция mathchStar, которая пытается сопоставить повторение символа <tex>c</tex>, начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет "кратчайшее совпадение", которое хорошо подходит для сопоставления с образцом, как в | + | # Если регулярное выражение имеет вид <tex>c*</tex>, то вызывается функция mathchStar, которая пытается сопоставить повторение символа <tex>c</tex>, начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет "кратчайшее совпадение", которое хорошо подходит для сопоставления с образцом, как в [http://ru.wikipedia.org/wiki/Grep '''grep'''], где нужно как можно быстрее найти совпадение. "Наидлиннейшее совпадение" более интуитивно и больше подходит для текстовых редакторов, где найденное заменят на что-то. Большинство современных библиотек для работы с регулярными выражениями предоставляют оба варианта. |
# Если регулярное выражение это <tex>\mathdollar</tex>, то оно допускает этот текст тогда и только тогда, когда текст закончился. | # Если регулярное выражение это <tex>\mathdollar</tex>, то оно допускает этот текст тогда и только тогда, когда текст закончился. | ||
# Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов <tex>\mathrm{matchHere}</tex>. | # Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов <tex>\mathrm{matchHere}</tex>. | ||
Строка 75: | Строка 75: | ||
'''for''' (i = 0; text[i] != '\0' '''and''' (text[i] == c '''or''' c == '.'); i++) | '''for''' (i = 0; text[i] != '\0' '''and''' (text[i] == c '''or''' c == '.'); i++) | ||
'''while''' i <tex>\geqslant</tex> 0 | '''while''' i <tex>\geqslant</tex> 0 | ||
− | '''if''' matchHere(regexp, text | + | '''if''' matchHere(regexp, text[i:]) |
'''return''' ''true'' | '''return''' ''true'' | ||
i-- | i-- | ||
Строка 85: | Строка 85: | ||
== Источники информации == | == Источники информации == | ||
* [http://www.cs.princeton.edu/courses/archive/spr10/cos333/beautiful.html A Regular Expression Matcher] | * [http://www.cs.princeton.edu/courses/archive/spr10/cos333/beautiful.html A Regular Expression Matcher] | ||
− | |||
− | |||
− | |||
[[Категория: Теория формальных языков]] | [[Категория: Теория формальных языков]] | ||
[[Категория: Автоматы и регулярные языки]] | [[Категория: Автоматы и регулярные языки]] |
Версия 23:45, 4 декабря 2016
Задача: |
Даны регулярное выражение и текст. Нужно проверить допускает ли регулярное выражение данный текст. |
Содержание
Алгоритм
Если требуется сопоставить простое регулярное выражение с текстом, то проще использовать текущий алгоритм, чем использовать недетерминированный конечный автомат, построенный по теореме Клини, так как он прост в написании, требует меньше памяти и на простых регулярных выражениях работает не сильно хуже.
Введем обозначения:
- — один любой буквенный символ,
- — один любой символ,
- — символ начала текста,
- — символ конца текста,
- — предыдущий символ встречается ноль или более раз.
Данный алгоритм можно использовать для регулярных выражений вида:
Псевдокод
// поиск вхождения регулярного выражения в любом месте текста
function match(regexp: String, text: String): boolean
if regexp[0] == '^'
return matchHere(regexp[1:], text) // regexp[n:] возвращает regexp без первых n элементов за O(1)
int i = 0
while i
text.length
if matchHere(regexp, text[1:])
return true
i++
return false
Функция
проверяет есть ли вхождение регулярного выражения в любом месте в пределах текста. Если существует более одного вхождения, то найдется самое левое и самое короткое.Логика функции
проста. Если — первый символ регулярного выражения, то любое возможное вхождение должно начинаться в начале текста. Т.е. если — регулярное выражение, то должно входить в текст только с первой позиции текста, а не где-то в середине текста. Это проверяется путем сопоставления остатка регулярного выражения с текстом, начиная с первой позиции и нигде более.В противном случае регулярное выражение может входить в текст в любой позиции. Это проверяется путем сопоставления регулярного выражения во всех позициях текста. Если регулярное выражение входит более одного раза в текст, то только самое левое вхождение будет распознано. Т.е. если
— регулярное выражение, то для него найдется самое левое вхождение в текст.// поиск вхождения регулярного выражения в начале текста function matchHere(regexp: String, text: String): boolean if regexp[0] == '\0' return true if regexp[1] == '*' // не будет выхода за пределы строки, так как в конце regexp и text всегда есть символ '\0' return matchStar(regexp[0], regexp[2:], text) if regexp[0] == '$' and regexp[1] == '\0' return text == '\0' if text[0] != '\0' and (regexp[0] == '.' or regexp[0] == text[0]) return matchHere(regexp[1:], text[1:]) return false
Основная часть работы сделана в
, которая сопоставляет регулярное выражение с текстом в текущей позиции. Функция пытается сопоставить первый символ регулярного выражения с первым символом текста. В случае успеха мы можем сравнить следующий символ регулярного выражения со следующим символом текста, вызвав рекурсивно. Иначе нет совпадения с регулярным выражением в текущей позиции текста.// сопоставление с регулярным выражением вида: c*
function matchStar(c: char, regexp: String, text: String): boolean
int i = 0
while i
text.length and (text[i] == c or c == '.')
if matchHere(regexp, text[i:])
return true
i++
return false
Рассмотрим возможные случаи:
- Если в ходе рекурсии регулярное выражение осталось пустым , то текст допускается этим регулярным выражением.
- Если регулярное выражение имеет вид grep, где нужно как можно быстрее найти совпадение. "Наидлиннейшее совпадение" более интуитивно и больше подходит для текстовых редакторов, где найденное заменят на что-то. Большинство современных библиотек для работы с регулярными выражениями предоставляют оба варианта. , то вызывается функция mathchStar, которая пытается сопоставить повторение символа , начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет "кратчайшее совпадение", которое хорошо подходит для сопоставления с образцом, как в
- Если регулярное выражение это , то оно допускает этот текст тогда и только тогда, когда текст закончился.
- Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов .
- Если все предыдущие попытки найти совпадения провалились, то никакая подстрока из текста не допускается регулярным выражением.
Модификации
Немного изменим функцию
для поиск самого левого и самого длинного вхождения :- Найдем максимальную последовательность подряд идущих символов . Назовем ее .
- Сопоставим часть текста без с остатком регулярного выражения.
- Если части совпали, то текст допускается этим регулярным выражением. Иначе, если пусто, то текст не допускается этим регулярным выражением, иначе убираем один символ из и повторяем шаг 2.
Псевдокод
function matchStar(c: char, regexp: String, text: String): boolean
int i
for (i = 0; text[i] != '\0' and (text[i] == c or c == '.'); i++)
while i
0
if matchHere(regexp, text[i:])
return true
i--
return false