Простой сопоставитель регулярных выражений — различия между версиями
Shersh (обсуждение | вклад) м (переименовал Простой матчер регулярных выражений в Простой сопоставитель регулярных выражений) |
|||
| Строка 13: | Строка 13: | ||
=== Псевдокод === | === Псевдокод === | ||
| − | '''function''' match(regexp: ''' | + | <font color=darkgreen>// поиск вхождения регулярного выражения в любом месте текста</font> |
| − | '''if''' | + | '''function''' match(regexp: '''string''', text: '''string'''): '''boolean''' |
| − | '''return''' matchHere(regexp | + | '''if''' regexp[0] == '^' |
| − | ''' | + | '''return''' matchHere(regexp.drop(1), text) <font color=darkgreen>// drop(n) возвращает строку без первых n элементов</font> |
| − | '''if''' | + | '''int''' i = 0 |
| + | '''while''' i <tex>\leqslant</tex> text.length | ||
| + | '''if''' matchHere(regexp, text.drop(i)) | ||
'''return''' ''true'' | '''return''' ''true'' | ||
| − | + | i++ | |
'''return''' ''false'' | '''return''' ''false'' | ||
| − | '''function''' matchHere(regexp: ''' | + | <font color=darkgreen>// поиск вхождения регулярного выражения в начале текста</font> |
| − | '''if''' | + | '''function''' matchHere(regexp: '''string''', text: '''string'''): '''boolean''' |
| + | '''if''' regexp[0] == '\0' | ||
'''return''' ''true'' | '''return''' ''true'' | ||
| − | '''if''' | + | '''if''' regexp[1] == '*' |
| − | '''return''' matchStar(regexp[0], regexp | + | '''return''' matchStar(regexp[0], regexp.drop(2), text) |
| − | '''if''' | + | '''if''' regexp[0] == '$' '''and''' regexp[1] == '\0' |
| − | '''return''' | + | '''return''' text == '\0'; |
| − | '''if''' | + | '''if''' text[0] != '\0' '''and''' (regexp[0] == '.' '''or''' regexp[0] == text[0]) |
| − | '''return''' matchHere(regexp | + | '''return''' matchHere(regexp.drop(1), text.drop(1)) |
'''return''' ''false'' | '''return''' ''false'' | ||
| − | '''function''' matchStar(c : '''char''', regexp: ''' | + | <font color=darkgreen>// сопоставление с регулярным выражением вида: c*</font> |
| − | + | '''function''' matchStar(c : '''char''', regexp: '''string''', text: '''string'''): '''boolean''' | |
| − | '''if''' | + | '''int''' i = 0 |
| + | '''while''' i <tex>\leqslant</tex> text.length '''and''' (text[i] == c '''or''' c == '.') | ||
| + | '''if''' matchHere(regexp, text.drop(i)) | ||
'''return''' ''true'' | '''return''' ''true'' | ||
| − | + | i++ | |
'''return''' ''false'' | '''return''' ''false'' | ||
| Строка 52: | Строка 57: | ||
Рассмотрим возможные случаи: | Рассмотрим возможные случаи: | ||
# Если в ходе рекурсии регулярное выражение осталось пустым <tex>(regexp[0] == \backslash0)</tex>, то текст допускается этим регулярным выражением. | # Если в ходе рекурсии регулярное выражение осталось пустым <tex>(regexp[0] == \backslash0)</tex>, то текст допускается этим регулярным выражением. | ||
| − | # Если регулярное выражение имеет вид <tex>c*</tex>, то вызывается функция mathchStar, которая пытается сопоставить повторение символа <tex>c</tex>, начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет "кратчайшее совпадение", которое хорошо подходит для сопоставления с образцом, как в [https://ru.wikipedia.org/wiki/Grep | + | # Если регулярное выражение имеет вид <tex>c*</tex>, то вызывается функция mathchStar, которая пытается сопоставить повторение символа <tex>c</tex>, начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет "кратчайшее совпадение", которое хорошо подходит для сопоставления с образцом, как в '''grep'''<ref>[https://ru.wikipedia.org/wiki/Grep grep]</ref>, где нужно как можно быстрее найти совпадение. "Наидлиннейшее совпадение" более интуитивно и больше подходит для текстовых редакторов, где найденное заменят на что-то. Большинство современных библиотек для работы с регулярными выражениями предоставляют оба варианта. |
# Если регулярное выражение это <tex>\mathdollar</tex>, то оно допускает этот текст тогда и только тогда, когда текст закончился. | # Если регулярное выражение это <tex>\mathdollar</tex>, то оно допускает этот текст тогда и только тогда, когда текст закончился. | ||
# Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов <tex>\mathrm{matchHere}</tex>. | # Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов <tex>\mathrm{matchHere}</tex>. | ||
| Строка 58: | Строка 63: | ||
| − | Данный алгоритм прост и лаконичен, но у него есть недостаток. Для регулярного выражения содержащего несколько <tex>.*</tex> подряд этот алгоритм может работать очень медленно. Рассмотрим время работы '''grep'а''' (наш алгоритм схож со стандартным '''grep'ом'''). Например команда: "<tex>grep</tex> <tex>a.*a.*a.*a.a</tex>" потребует 20 секунд, чтобы обработать текстовой файл размером 4MB на обычной машине. В то же время алгоритм, который конвертирует [[Недетерминированные конечные автоматы|недетерминированный конечный автомат]] в [[Детерминированные конечные автоматы|детерминированный конечный автомат]] (например, [https://ru.wikipedia.org/wiki/Grep | + | Данный алгоритм прост и лаконичен, но у него есть недостаток. Для регулярного выражения содержащего несколько <tex>.*</tex> подряд этот алгоритм может работать очень медленно. Рассмотрим время работы '''grep'а''' (наш алгоритм схож со стандартным '''grep'ом'''). Например команда: "<tex>grep</tex> <tex>a.*a.*a.*a.a</tex>" потребует 20 секунд, чтобы обработать текстовой файл размером 4MB на обычной машине. В то же время алгоритм, который конвертирует [[Недетерминированные конечные автоматы|недетерминированный конечный автомат]] в [[Детерминированные конечные автоматы|детерминированный конечный автомат]] (например, '''egrep'''<ref>[https://ru.wikipedia.org/wiki/Grep egrep]</ref>), потребует менее одной десятой доли секунды на обработку тех же данных. |
===Модификации=== | ===Модификации=== | ||
| Строка 67: | Строка 72: | ||
=== Псевдокод === | === Псевдокод === | ||
| − | '''function''' matchStar(c : '''char''', regexp: ''' | + | '''function''' matchStar(c : '''char''', regexp: '''string''', text: '''string'''): '''boolean''' |
| − | ''' | + | '''int''' i |
| − | '''for''' ( | + | '''for''' (i = 0; text[i] != '\0' '''and''' (text[i] == c '''or''' c == '.'); i++) |
| − | ''' | + | '''while''' i <tex>\geqslant</tex> 0 |
| − | '''if''' | + | '''if''' matchHere(regexp, text.drop(i)) |
'''return''' ''true'' | '''return''' ''true'' | ||
| − | + | i-- | |
'''return''' ''false'' | '''return''' ''false'' | ||
| Строка 82: | Строка 87: | ||
type: '''int''' <font color=darkgreen>/ тип элемента: STAR, QUESTION, PLUS, SYMBOL, ...</font> | type: '''int''' <font color=darkgreen>/ тип элемента: STAR, QUESTION, PLUS, SYMBOL, ...</font> | ||
c: '''char''' <font color=darkgreen>/ сам символ</font> | c: '''char''' <font color=darkgreen>/ сам символ</font> | ||
| − | cs: '''char''' | + | cs: '''char'''[] <font color=darkgreen>/ для случая [...]</font> |
ncs: '''bool''' <font color=darkgreen>/ для случая отрицания cs: [^...]</font> | ncs: '''bool''' <font color=darkgreen>/ для случая отрицания cs: [^...]</font> | ||
| Строка 90: | Строка 95: | ||
== Источники информации == | == Источники информации == | ||
* [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] | ||
| + | |||
| + | == Примечания == | ||
| + | <references/> | ||
[[Категория: Теория формальных языков]] | [[Категория: Теория формальных языков]] | ||
[[Категория: Автоматы и регулярные языки]] | [[Категория: Автоматы и регулярные языки]] | ||
Версия 21:23, 3 декабря 2016
| Задача: |
| Даны регулярное выражение и текст. Нужно проверить допускает ли регулярное выражение данный текст. |
Содержание
Алгоритм
Введем обозначения:
- — один любой буквенный символ
- — один любой символ
- — символ начала текста
- — символ конца текста
- — предыдущий символ встречается ноль или более раз
Псевдокод
// поиск вхождения регулярного выражения в любом месте текста
function match(regexp: string, text: string): boolean
if regexp[0] == '^'
return matchHere(regexp.drop(1), text) // drop(n) возвращает строку без первых n элементов
int i = 0
while i text.length
if matchHere(regexp, text.drop(i))
return true
i++
return false
// поиск вхождения регулярного выражения в начале текста
function matchHere(regexp: string, text: string): boolean
if regexp[0] == '\0'
return true
if regexp[1] == '*'
return matchStar(regexp[0], regexp.drop(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.drop(1), text.drop(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.drop(i))
return true
i++
return false
Данный псевдокод использует указатели и арифметические операции над ними из языка C.
Функция проверяет есть ли вхождение регулярного выражения в любом месте в пределах текста. Если существует более одного вхождения, то найдется самое левое и самое короткое.
Логика функции проста. Если — первый символ регулярного выражения, то любое возможное вхождение должно начинаться в начале текста. Т.е. если — регулярное выражение, то должно входить в текст только с первой позиции текста, а не где-то в середине текста. Это проверяется путем сопоставления остатка регулярного выражения с текстом, начиная с первой позиции и нигде более.
В противном случае регулярное выражение может входить в текст в любой позиции. Это проверяется путем сопоставления регулярного выражения во всех позициях текста. Если регулярное выражение входит более одного раза в текст, то только самое левое вхождение будет распознано. Т.е. если — регулярное выражение, то для него найдется самое левое вхождение в текст.
Основная часть работы сделана в , которая сопоставляет регулярное выражение с текстом в текущей позиции. Функция пытается сопоставить первый символ регулярного выражения с первым символом текста. В случае успеха мы можем сравнить следующий символ регулярного выражения со следующим символом текста, вызвав рекурсивно. Иначе нет совпадения с регулярным выражением в текущей позиции текста.
Рассмотрим возможные случаи:
- Если в ходе рекурсии регулярное выражение осталось пустым , то текст допускается этим регулярным выражением.
- Если регулярное выражение имеет вид , то вызывается функция mathchStar, которая пытается сопоставить повторение символа , начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет "кратчайшее совпадение", которое хорошо подходит для сопоставления с образцом, как в grep[1], где нужно как можно быстрее найти совпадение. "Наидлиннейшее совпадение" более интуитивно и больше подходит для текстовых редакторов, где найденное заменят на что-то. Большинство современных библиотек для работы с регулярными выражениями предоставляют оба варианта.
- Если регулярное выражение это , то оно допускает этот текст тогда и только тогда, когда текст закончился.
- Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов .
- Если все предыдущие попытки найти совпадения провалились, то никакая подстрока из текста не допускается регулярным выражением.
Данный алгоритм прост и лаконичен, но у него есть недостаток. Для регулярного выражения содержащего несколько подряд этот алгоритм может работать очень медленно. Рассмотрим время работы grep'а (наш алгоритм схож со стандартным grep'ом). Например команда: " " потребует 20 секунд, чтобы обработать текстовой файл размером 4MB на обычной машине. В то же время алгоритм, который конвертирует недетерминированный конечный автомат в детерминированный конечный автомат (например, egrep[2]), потребует менее одной десятой доли секунды на обработку тех же данных.
Модификации
Немного изменим функцию для поиск самого левого и самого длинного вхождения :
- Найдем максимальную последовательность подряд идущих символов . Назовем ее .
- Сопоставим часть текста без с остатком регулярного выражения.
- Если части совпали, то текст допускается этим регулярным выражением. Иначе, если пусто, то текст не допускается этим регулярным выражением, иначе убираем один символ из и повторяем шаг 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.drop(i))
return true
i--
return false
Увеличить количество символов из которых может состоять регулярное выражение можно, задавая регулярное выражение последовательностью структур, описывающих каждый ее элемент.
Псевдокод
struct Token
type: int / тип элемента: STAR, QUESTION, PLUS, SYMBOL, ...
c: char / сам символ
cs: char[] / для случая [...]
ncs: bool / для случая отрицания cs: [^...]