Сообщений 24    Оценка 308        Оценить  
Система Orphus

Как проверить, является ли строка числом, e-mail'ом?

Автор: Николай Меркин
Опубликовано: 19.01.2002
Исправлено: 13.03.2005
Версия текста: 1.0
Предисловие. Формальные языки и конечные автоматы
Грамматика языка. Формы записи.
Конечные автоматы
Автоматная грамматика. Порождающий автомат.
Разбирающий автомат
Автоматы для регулярных выражений
Структура материала
Целое число
Число с плавающей точкой
Реализация автомата на switch
Реализация с анализом типов символов
Реализация на таблице
E-mail
Реализация автомата на switch
Реализация с анализом типов символов
Реализация на таблице
Сравнение скорости работы
Число с плавающей точкой
E-mail

Предисловие. Формальные языки и конечные автоматы

Множества всех строк, записанных по некоторым правилам, называются формальными языками. Так, любой язык программирования, язык написания чисел и адресов e-mail - это формальные языки.

Грамматика языка. Формы записи.

Формальный язык описывается с помощью так называемых "правил вывода", которые позволяют построить все возможные строки языка (хотя обычно никто такую задачу не ставит). Правила вывода составляют "грамматику языка".

Наиболее известная форма представления грамматики - это "форма Бэкуса-Наура" (БНФ): множество правил вида

<элемент> ::= цепочка из символов и/или <элементов>

что означает: в исходной цепочке можно заменить <элемент> (некоторый метасимвол, не входящий в алфавит языка) на другую цепочку.

Для сокращения записи используются регулярные выражения:

БНФ с элементами регулярных выражений называют Расширенной БНФ (РБНФ).

Конечные автоматы

Конечный автомат (КА) - устройство, имеющее конечный набор состояний (им в данном случае соответствуют метасимволы), и конечный набор реакций на конечные же наборы входных и выходных сигналов (алфавиты).

состояние, входной сигнал -> новое состояние, выходной сигнал.

Программа, реализующая КА, очень проста. Номер (идентификатор) состояния хранится в регистре. При подаче входного сигнала находится правило с совпадающими текущим состоянием и входом. В соответствии с правилом, устанавливается новое состояние и выдается выходной сигнал.

Если есть разные правила с одинаковым условием, такой автомат называется недетерминированным (фактически, непредсказуемым). Если же у всех правил условия различаются - детерминированным (однозначным).

Детерминированные КА (ДКА) можно реализовать как алгоритмически (последовательная проверка условий), так и в табличной либо функциональной форме:

новое_состояние [ состояние, входной_сигнал ]
выходной_сигнал [ состояние, входной_сигнал ]

Автоматная грамматика. Порождающий автомат.

Грамматику языка, состоящую только из правил вида

<A> ::= символ <B>

(или которую можно привести к такому виду), называют "автоматной", так как существует конечный автомат, порождающий любые цепочки данного языка.

Автомат, порождающий строки языка, в общем случае является недетерминированным (на его входе, фактически, тактирующие импульсы). То есть, имея правила

A -> x B
A -> y C

Можем из состояния А выдать как x, так и y.

Собственно, это и приводит к богатству языка (ДКА может порождать только либо бесконечную строку из одинаковых символов, либо 1-символьную, либо пустую).

Разбирающий автомат

Обработка исходной строки сводится к следующим задачам:

Естественно, что трансляция включает в себя проверку: если строка неправильна, то перевести ее не удастся.

Для задач разбора (parsing, синтаксический анализ), то есть проверки и трансляции, строится автомат, на вход которого посимвольно подается исходная строка, а на выходе - какие-либо диагностические сигналы (правильно/неправильно/продолжать).

Этот автомат должен быть детерминированным (однозначным); в противном случае его работа непредсказуема.

ПРИМЕЧАНИЕ

Вообще, разбирающий автомат может не быть конечным (так, для Паскаля и Си используются "магазинные" (стековые) автоматы, а для Фортрана и этого недостаточно).

Фактически, такой автомат получается из порождающего:

порождающий: A, tact -> x, B
разбирающий: A, x -> B, diagnostics

Если получился недетерминированный автомат, то есть

A, x -> B
A, x -> C

то порождающий автомат можно перепроектировать, чтобы этого не было:

A -> x, B заменяется на A -> x, N (вводим новое состояние)
A -> x, C заменяется на A -> x, N

Ко всем правилам, содержащим в условии B и С, добавляются новые:

B -> y, D добавляется N -> y, D

Автоматы для регулярных выражений

Ниже приведены способы написания правил для случаев, которые нас интересуют.

Выражение Правила порождения Правила разбора
последовательность
x1 x2 x3 ...
R0 -> x1, Rx1
Rx1 -> x2, Rx2
...
R0, x1 -> Rx1
Rx1, x2 -> Rx2
...
ветвление
x1 x_branch | y1 y_branch | ...
R0 -> x1, Rx
R0 -> y1, Ry
...
R0, x1 -> Rx
R0, y1 -> Ry
...
условная вставка
[ x1 x_branch ] z1 ...
эквивалентно
x1 x_branch z1... | z1...
R0 -> x1, Rx
R0 -> z1, Rz
R0, x1 -> Rx
R0, z1 -> Rz
повторение
( x1 ... xn )* z1...
R0 -> x1, Rx1
R0 -> z1, Rz
Rxn -> x1, Rx1
Rxn -> z1, Rz
R0, x1 -> Rx1
R0, z1 -> Rz
Rxn, x1 -> Rx1
Rxn, z1 -> Rz

Здесь, R0 - исходное состояние перед порождением/разбором строки, а под z1 имеются в виду первые символы, которые могут идти в выражении, следующим за скобками.

Структура материала

Для каждого случая (целое число, вещественное число, адрес e-mail):

В БНФ, регулярных выражениях и сводах правил используется следующая нотация. Метасимволы и состояния - пишутся с большой буквы, базовые множества символов (такие как <digit> - цифры) - с маленькой. При этом имеем в виду, что

A, <digit> -> B

развертывается в

A, 0 -> B; ... A, 9 -> B

Итак, рассмотрим синтаксис искомых строк.

Целое число

Самый простой случай - десятичное число (или в любой другой, наперед заданной системе счисления):

БНФ

<Number> ::= [ <sign> ] <Digits>
<Digits> ::= <digit> [ <Digits> ]

Регулярное выражение

<Number> ::= <sign> <digit> <digit>*

Получаем достаточно очевидное решение (функции isSign, isDigit, isEnd могут быть реализованы любым способом, но наиболее простой способ - это сравнения).

Листинг 1. Функция checkInt

Число с плавающей точкой

БНФ

<Real_number> ::= <Mantissa> [ <E> <Exponent> ]
<Mantissa> :: = [<sign>] <Integer_part> [ <dot> [<Fractional_part>] ]
<Exponent> ::= [<sign>] <Unsigned_number>
<Integer_part> ::= <Unsigned_number>
<Fractional_part> ::= <Unsigned_number>
<Unsigned_number> ::= <digit> <digit>*
<sign> ::= + | -
<e> ::= E | e
<dot> ::= .

Регулярное выражение

[<sign>] <digit> <digit>* [<dot> <digit>* ] [ <e> [<sign>] <digit> <digit>* ]

Правила для автомата

Start = Mantissa1
Mantissa1 - начало мантиссы
	Mantissa1, <sign> -> Mantissa2
	Mantissa1, <digit> -> Mantissa3
Mantissa2 - начало целой части мантиссы
	Mantissa2, <digit> -> Mantissa3
	Mantissa2, <end> -> Success
Mantissa3 - наличие хотя бы одной цифры
	Mantissa3, <digit> -> Mantissa3
	Mantissa3, <dot> -> Mantissa4
	Mantissa3, <e> -> Exponent1
	Mantissa3, <end> -> Success
Mantissa4 - дробная часть
	Mantissa4, <digit> -> Mantissa4
	Mantissa4, <e> -> Indicator1
	Mantissa4, <end> -> Success
Exponent1 - начало порядка
	Exponent1, <sign> -> Exponent2
	Exponent1, <digit> -> Exponent3
Exponent2 - начало числа в порядке
	Exponent2, <digit> -> Exponent3
	Exponent2, <end> -> Success
Exponent3 - продолжение числа в порядке
	Exponent3, <digit> -> Exponent3
	Exponent3, <end> -> Success
(Во всех остальных случаях -> Error)

Реализация автомата на switch

Возможны два способа построения:

Первый способ экономнее, так как для каждого состояния валидными являются не все типы символов.

Листинг 2. Функция checkFloat_S

С другой стороны, если число типов символов меньше, чем число состояний, то можно пойти по второму пути.

Реализация с анализом типов символов

Листинг 3. Функция checkFloat_C

Реализация на таблице

Табличная реализация ускоряет работу, так как вместо множества проверок выполняются лишь:

Листинг 4. Функция checkFloat_T

E-mail

БНФ

<Email> ::= <Name> @ <Domain>
<Name> ::= <Words>
<Domain> ::= <Words>
<Words> ::= <Word> [  . <Words> ]
<Word> ::= <letter> <letter>*
<letter> - любые символы в диапазоне #33 (-) до #126 (~), кроме ()<>@,;:\/.[] (согласно RFC 822)

Регулярное выражение

<letter> <letter>* ( <dot> <letter> <letter>* )* <at> <letter> <letter>* (
<dot> <letter> <letter>* )*

Правила для автомата

Start = Name1
Name1 - начало слова в имени (должен быть буквенно-цифровой символ)
	Name1, <letter> -> Name2
Name2 - продолжение слова
	Name2, <letter> -> Name2
	Name2, <dot> -> Name1 (после точки должно быть новое слово)
	Name2, <at> -> Domain1
Domain1 - начало имени в домене
	Domain1, <letter> -> Domain2
Domain2 - продолжение домена
	Domain2, <letter> -> Domain2
	Domain2, <dot> -> Domain1
	Domain2, <end> -> Success
(Во всех остальных случаях -> Error)

Реализация автомата на switch

Листинг 5. Функция checkEmail_S

Реализация с анализом типов символов

Листинг 6. Функция checkEmail_C

Реализация на таблице

Листинг 7. Функция checkEmail_T

Сравнение скорости работы

Для сравнения была написана "бенчмарка", которая вычисляла каждую из функций 100.000 раз, измеряя длительность работы в тиках (1 тик = 55 мс). Испытания проводились на Pentium-II-400, под управлением Windows 2000. Компилятор - MSVC-6 (без оптимизации). Погрешность измерений составляет приблизительно 10 тиков (0.5 с), что связано, по-видимому, с загрузкой компьютера фоновыми процессами. Измерения проводились как для правильных, так и неправильных строк (тем самым, проверяется скорость принятия решения).

Превосходство табличного метода - налицо.

Число с плавающей точкой

Заодно проверим скорость системных функций...

Исходная строка checkFloat_S (switch) checkFloat_C (char types) checkFloat_T (table) atof sscanf
1 160 90 70 440 530
111 220 170 120 540 720
-1.2e-3 330 510 210 860 1050
+1.2E+3 321 521 210 840 1040
+ 70 110 80 100 110
. 70 100 40 80 110
e 80 120 50 80 110
? 70 150 40 80 110

E-mail

Исходная строка checkEmail_S (switch) checkEmail_C (char types) checkEmail_T (table)
a@b 220 290 130
a.b@c 300 360 170
a.b@c.d 390 470 210
@ 60 130 50
a. 150 180 90
a..b@c.d 130 220 90
? 50 140 40


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 24    Оценка 308        Оценить