Я уже отдал +1 марку Байерсу, но, насколько я помню, на бумаге не так много говорится о том, как работает регулярное выражение, объясняя, почему один алгоритм плох, а другой намного лучше. Может быть, что-то в ссылках?
Я сосредоточусь на хорошем подходе - создании конечных автоматов. Если вы ограничиваете себя детерминированными автоматами без минимизации, это не слишком сложно.
Что я буду очень быстро описывать, это подход, принятый в Modern Compiler Design.
Представьте, что вы есть следующее регулярное выражение ...
a (b c)* d
Буквы представляют буквенных символов, чтобы соответствовать. * - обычное совпадение с нулем или большим числом повторений. Основная идея - вывести состояния, основанные на пунктирных правилах. Государственный ноль мы возьмем в качестве государства, в котором ничего не было подкреплено еще, так что точка уходит на фронт ...
0 : .a (b c)* d
Единственный возможный матч «а», так что следующее состояние, которое мы получаем это. ..
1 : a.(b c)* d
Теперь мы имеем две возможности - соответствовать «B» (если есть по крайней мере один повтор «B C») или совпадать с «D» в противном случае. Примечание. В основном мы делаем поиск по орграфу здесь (сначала по глубине или по ширине, или по другому), но мы обнаруживаем орграф, когда мы его ищем. Предполагая, что стратегия в ширину, нам нужно будет поставить очередь на один из наших случаев для последующего рассмотрения, но я проигнорирую этот вопрос здесь. Во всяком случае, мы обнаружили два новых состояния ...
2 : a (b.c)* d
3 : a (b c)* d.
Состояние 3 - конечное состояние (может быть более одного). Для состояния 2 мы можем сопоставлять только «c», но после этого мы должны быть осторожны с точкой. Мы получаем «a. (B c) * d» - то же, что и состояние 1, поэтому нам не нужно новое состояние.
IIRC, подход в Modern Compiler Design заключается в том, чтобы перевести правило, когда вы нажмете на оператора, чтобы упростить обработку точки. Государство 1 будет преобразовано в ...
1 : a.b c (b c)* d
a.d
То есть, ваш следующий вариант либо в соответствии с первым повторением или пропустить повторение. Следующие состояния из этого эквивалентны состояниям 2 и 3. Преимущество такого подхода состоит в том, что вы можете отбросить все свои прошлые матчи (все до «.»), Поскольку вы заботитесь только о будущих матчах. Обычно это дает меньшую модель состояния (но не обязательно минимальную).
EDIT Если вы отбрасываете уже согласованные детали, ваше описание состояния представляет собой представление набора строк, которое может произойти с этой точки.
В терминах абстрактной алгебры это своего рода набор замыканий. Алгебра в основном представляет собой набор с одним (или более) оператором. Наш набор - описания состояний, а наши операторы - наши переходы (совпадения персонажей). Закрытое множество - это приложение, в котором применение любого оператора к любому члену в наборе всегда создает другой элемент, который находится в наборе. Закрытие множества - это конечное множество, которое является замкнутым. Итак, в основном, начиная с очевидного состояния начала, мы строим минимальное множество состояний, которое замкнуто относительно нашего набора операторов перехода - минимального множества достижимых состояний.
Минимальное значение здесь означает процесс замыкания - могут быть меньшие эквивалентные автоматы, которые обычно называются минимальными.
Учитывая эту основную идею, не так уж сложно сказать «если у меня есть две машины состояний, представляющие два набора строк, как я получу третий, представляющий объединение» (или пересечение, или установить разницу ...). Вместо пунктирных правил ваши представления состояния будут текущим состоянием (или набором текущих состояний) с каждого входного автомата и, возможно, дополнительной информацией.
Если ваши обычные грамматики становятся сложными, вы можете свести к минимуму. Основная идея здесь относительно проста. Вы группируете все свои состояния в один класс эквивалентности или «блок». Затем вы неоднократно проверяете, нужно ли разделять блоки (состояния на самом деле не эквивалентны) по отношению к определенному типу перехода. Если все состояния в определенном блоке могут принимать совпадение одного и того же символа и при этом достигать одного и того же следующего блока, они эквивалентны.
Алгоритм Hopcrofts - эффективный способ справиться с этой основной идеей.
Особенно интересно, что минимизация состоит в том, что каждый детерминированный конечный автомат имеет ровно одну минимальную форму. Кроме того, алгоритм Hopcrofts будет давать то же представление этой минимальной формы, независимо от того, какое представление о том, в каком большем случае это началось. То есть это «каноническое» представление, которое может быть использовано для получения хэша или для произвольно-непротиворечивых порядков. Это означает, что вы можете использовать минимальные автоматы как ключи в контейнерах.
Вышеупомянутые, вероятно, немного неряшливые определения WRT, поэтому убедитесь, что вы сами изучаете любые термины, прежде чем использовать их самостоятельно, но с некоторой удачей это дает справедливое быстрое введение в основные идеи.
BTW - посмотрите на остальную часть Dick Grunes site - у него есть бесплатная книга PDF по методам разбора. Первое издание Modern Compiler Design довольно неплохое ИМО, но, как вы увидите, появляется второе издание.
+1 Интересная мысль. Вы будете экспертом в регулярных выражениях, если вы это сделаете;) –
[интересный] (http://www.codeproject.com/KB/recipes/OwnRegExpressionsParser.aspx#Seven) статья о том, как создать упрощенный анализатор RE (не Python related)) – systempuntoout
http://perl.plover.com/Regex/article.html - объяснение двигателей регулярных выражений с использованием автоматов. Вы также можете рассмотреть более простой проект, который был поднят здесь некоторое время назад, что должно писать переводчик с регулярным выражением на английский. Например, '(foo | bar) (baz) +' должен перевести на «либо« foo », либо« bar », затем один или несколько« baz ». Pyparsing (http://pyparsing.wikispaces.com/Documentation) может помочь с этим. – katrielalex