Изменения

Перейти к: навигация, поиск

Простой сопоставитель регулярных выражений

794 байта добавлено, 21:47, 4 декабря 2016
Нет описания правки
==Алгоритм==
Если требуется сопоставить простое регулярное выражение с текстом, то проще использовать текущий алгоритм, чем использовать [[Недетерминированные конечные автоматы| недетерминированный конечный автомат]], построенный по [[Теорема Клини (совпадение классов автоматных и регулярных языков)| теореме Клини]], так как он прост в написании, требует меньше памяти и на простых регулярных выражениях работает не сильно хуже.
 
Введем обозначения:
:<tex>c</tex> {{---}} один любой буквенный символ
:<tex>\mathdollar</tex> {{---}} символ конца текста
:<tex>*</tex> {{---}} предыдущий символ встречается ноль или более раз
 
Данный алгоритм можно использовать для регулярных выражений вида: <tex>\wedge? (c | \ldotp | c^* | \ldotp^*)^* \mathdollar?</tex>
=== Псевдокод ===
<font color=darkgreen>// поиск вхождения регулярного выражения в любом месте текста</font>
'''function''' match(regexp: '''stringString''', text: '''stringString'''): '''boolean'''
'''if''' regexp[0] == '^'
'''return''' matchHere(regexp.drop(1), text) <font color=darkgreen>// drop(n) возвращает строку без первых n элементов</font>
i++
'''return''' ''false''
Функция <tex>\mathrm{match(regexp, text)}</tex> проверяет есть ли вхождение регулярного выражения в любом месте в пределах текста. Если существует более одного вхождения, то найдется самое левое и самое короткое.  Логика функции <tex>\mathrm{match}</tex> проста. Если <tex>\wedge</tex> {{---}} первый символ регулярного выражения, то любое возможное вхождение должно начинаться в начале текста. Т.е. если <tex>\wedge</tex><tex>abc</tex> {{---}} регулярное выражение, то <tex>abc</tex> должно входить в текст только с первой позиции текста, а не где-то в середине текста. Это проверяется путем сопоставления остатка регулярного выражения с текстом, начиная с первой позиции и нигде более. В противном случае регулярное выражение может входить в текст в любой позиции. Это проверяется путем сопоставления регулярного выражения во всех позициях текста. Если регулярное выражение входит более одного раза в текст, то только самое левое вхождение будет распознано. Т.е. если <tex>abc</tex> {{---}} регулярное выражение, то для него найдется самое левое вхождение в текст. 
<font color=darkgreen>// поиск вхождения регулярного выражения в начале текста</font>
'''function''' matchHere(regexp: '''stringString''', text: '''stringString'''): '''boolean'''
'''if''' regexp[0] == '\0'
'''return''' ''true''
'''return''' matchHere(regexp.drop(1), text.drop(1))
'''return''' ''false''
Основная часть работы сделана в <tex>\mathrm{matchHere(regexp, text)}</tex>, которая сопоставляет регулярное выражение с текстом в текущей позиции. Функция <tex>\mathrm{matchHere}</tex> пытается сопоставить первый символ регулярного выражения с первым символом текста. В случае успеха мы можем сравнить следующий символ регулярного выражения со следующим символом текста, вызвав <tex>\mathrm{matchHere}</tex> рекурсивно. Иначе нет совпадения с регулярным выражением в текущей позиции текста. 
<font color=darkgreen>// сопоставление с регулярным выражением вида: c*</font>
'''function''' matchStar(c : '''char''', regexp: '''stringString''', text: '''stringString'''): '''boolean'''
'''int''' i = 0
'''while''' i <tex>\leqslant</tex> text.length '''and''' (text[i] == c '''or''' c == '.')
i++
'''return''' ''false''
 
Данный псевдокод использует указатели и арифметические операции над ними из языка C.
 
Функция <tex>\mathrm{match(regexp, text)}</tex> проверяет есть ли вхождение регулярного выражения в любом месте в пределах текста. Если существует более одного вхождения, то найдется самое левое и самое короткое.
 
Логика функции <tex>\mathrm{match}</tex> проста. Если <tex>\wedge</tex> {{---}} первый символ регулярного выражения, то любое возможное вхождение должно начинаться в начале текста. Т.е. если <tex>\wedge</tex><tex>abc</tex> {{---}} регулярное выражение, то <tex>abc</tex> должно входить в текст только с первой позиции текста, а не где-то в середине текста. Это проверяется путем сопоставления остатка регулярного выражения с текстом, начиная с первой позиции и нигде более.
 
В противном случае регулярное выражение может входить в текст в любой позиции. Это проверяется путем сопоставления регулярного выражения во всех позициях текста. Если регулярное выражение входит более одного раза в текст, то только самое левое вхождение будет распознано. Т.е. если <tex>abc</tex> {{---}} регулярное выражение, то для него найдется самое левое вхождение в текст.
 
Основная часть работы сделана в <tex>\mathrm{matchHere(regexp, text)}</tex>, которая сопоставляет регулярное выражение с текстом в текущей позиции. Функция <tex>\mathrm{matchHere}</tex> пытается сопоставить первый символ регулярного выражения с первым символом текста. В случае успеха мы можем сравнить следующий символ регулярного выражения со следующим символом текста, вызвав <tex>\mathrm{matchHere}</tex> рекурсивно. Иначе нет совпадения с регулярным выражением в текущей позиции текста.
Рассмотрим возможные случаи:
# Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов <tex>\mathrm{matchHere}</tex>.
# Если все предыдущие попытки найти совпадения провалились, то никакая подстрока из текста не допускается регулярным выражением.
 
Данный алгоритм прост и лаконичен, но у него есть недостаток. Для регулярного выражения содержащего несколько <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>), потребует менее одной десятой доли секунды на обработку тех же данных.
=== Псевдокод ===
'''function''' matchStar(c : '''char''', regexp: '''stringString''', text: '''stringString'''): '''boolean'''
'''int''' i
'''for''' (i = 0; text[i] != '\0' '''and''' (text[i] == c '''or''' c == '.'); i++)
Анонимный участник

Навигация