В данной статье под термином «конечный автомат» подразумевается алгоритм, который может находиться в одном из небольшого количества состояний. «Состояние» - это некое условие, определяющее заданную взаимосвязь входных и выходных сигналов, а также входных сигналов и последующих состояний. Смышленый читатель сразу отметит, что конечные автоматы, описанные в данной статье, это автоматы Мили. Автомат Мили – это конечный автомат, где выходные сигналы являются функциями текущего состояния и входного сигнала, в отличие от автомата Мура, в котором выходные сигналы – это функции только состояния. В обоих случаях последующее состояние – это функция текущего состояния и входного сигнала.

Рассмотрим пример простого конечного автомата. Представьте, что в текстовой строке вам нужно распознать последовательность символов «//». На рисунке 1 показано, как это выполняется при помощи конечного автомата. Первое появление слеша не дает выходного сигнала, но приводит к тому, что автомат переходит во второе состояние. Если во втором состоянии автомат не находит слеша, он возвращается к первому, поскольку необходимо наличие 2-х слешей подряд. Если второй слеш найден, автомат выдает сигнал «готово».

Выясните, что необходимо заказчику.

Составьте диаграмму перехода состояний

Закодируйте «скелет» конечного автомата без детализации операций перехода.

Убедитесь, что переходы функционируют правильно.

Конкретизируйте детали переходов.

Проведите тест.

Пример конечного автомата

Рассмотрим более интересный пример конечного автомата - программу, контролирующую втягивание и выдвижение шасси самолета. Хотя у большинства самолетов эта процедура выполняется при помощи электрогидравлического управляющего механизма (просто потому, что на борту отсутствует компьютер), в некоторых случаях, например в беспилотных летательных аппаратах, стоит использовать программное управление.

Для начала разберемся с оборудованием. Шасси самолета состоит из передней опоры, основного левого шасси и основного правого шасси. Они приводятся в действие гидравлическим механизмом. Гидравлический насос с электроприводом подает давление на силовой исполнительный механизм. При помощи программного обеспечения насос включается или выключается. Компьютер регулирует положение клапана направления - «вниз» или «вверх», чтобы позволить давлению поднять или опустить шасси. Каждая из опор шасси имеет концевой выключатель: один из них закрывается, если шасси поднято, другой - если оно зафиксировано в положении «вниз». Чтобы определить, находится ли самолет на земле, концевой выключатель на стойке передней опоры замыкается, если вес самолета приходится на переднюю опору. Средства управления пилота состоят из верхнего/нижнего рычага шасси и трех лампочек (по одной на каждую опору), которые могут выключаться, загораться зеленым (положение «вниз») или красным светом (положение «переход»).

А теперь перейдем к разработке конечного автомата. Первый, и самый сложный шаг – это понять реальные ожидания заказчика. Одним из преимуществ конечного автомата состоит в том, что он заставляет программиста продумывать все возможные случаи и, как следствие, получать от заказчика всю требуемую информацию. Почему я считаю это самым сложным этапом? А сколько раз вам давали описание задачи подобное этому: не задвигайте шасси, если самолет находится на земле.

Безусловно, это важно, но заказчик считает, что на этом все заканчивается. А как насчет остальных случаев? Достаточно ли задвигать шасси в тот момент, когда самолет отрывается от земли? Что, если самолет подскочит на кочке на взлетно-посадочной полосе? Что, если пилот переведет рычаг переключения скоростей в положение «вверх» во время парковки и, как следствие, начнет взлетать? Должно ли шасси в этом случае подняться?

Одним из преимуществ мышления в терминах конечного автомата является то, что вы можете быстро нарисовать диаграмму перехода состояний на проекционной доске, прямо перед заказчиком, и пройти весь процесс вместе с ним. Принято такое обозначение перехода состояний: «событие, которое явилось причиной перехода»/«выходной сигнал как результат перехода». Если бы мы разрабатывали только то, о чем изначально просил заказчик («не задвигать шасси, если самолет находится на земле»), то мы бы получили автомат, изображенный на рисунке 2.

При составлении диаграммы перехода состояний (или любого другого алгоритма) помните о следующем:

Компьютеры работают очень быстро по сравнению с механической аппаратурой

Инженер-механик, который объясняет, что требуется сделать, возможно, не знает о компьютерах и алгоритмах всего того, что знаете вы. И это тоже положительный момент, в противном случае, вы бы не потребовались!

Как поведет себя ваша программа, если сломается механическая или электрическая деталь?

Конечный автомат, основанный на том, что действительно требуется заказчику, показан на рисунке 3. Здесь мы хотим воспрепятствовать втягиванию шасси самолета до тех пор, пока он точно не будет в воздухе. Для этого после открытия переключателя приземления автомат в течение нескольких секунд находится в ожидании. Мы также хотим реагировать на нарастающий фронт рычага пилота, а не на уровень, что позволит избежать проблем, если кто-то подвинет рычаг, пока самолет находится на парковке. Втягивание или выдвижение шасси занимает несколько секунд, и мы должны быть готовы к ситуации, что пилот в процессе этой операции передумает и переместит рычаг в противоположном направлении. Также обратите внимание, что если самолет снова приземлится, пока мы находимся в состоянии «Waiting for takeoff», таймер перезапустится и втягивание шасси произойдет, только если самолет будет находиться в воздухе в течение 2-х секунд.

Реализация конечного автомата

Листинг 1 – это моя реализация конечного автомата изображенного на рисунке 3. Давайте обсудим некоторые детали кода.

/*листинг 1*/

typedef enum {GEAR_DOWN = 0, WTG_FOR_TKOFF, RAISING_GEAR, GEAR_UP, LOWERING_GEAR} State_Type;

/*Этот массив содержит указатели на функции, вызываемые в определенных состояниях*/

void (*state_table)() = {GearDown, WtgForTakeoff, RaisingGear, GearUp, LoweringGear};

State_Type curr_state;

InitializeLdgGearSM();

/*Сердце автомата – этот бесконечный цикл. Функция, соответствующая

Текущему состоянию, вызывается один раз в итерацию */

while (1) {

state_table();

DecrementTimer();

void InitializeLdgGearSM(void )

curr_state = GEAR_DOWN;

/*Остановка аппаратуры, выключение лампочек и т.д.*/

void GearDown(void )

/* Переходим в состояние ожидания, если самолет

Не на земле и поступила команда поднять шасси*/

if ((gear_lever == UP) && (prev_gear_lever == DOWN) && (squat_switch == UP)) {

curr_state = WTG_FOR_TKOFF;

prev_gear_lever = gear_lever;

void RaisingGear(void )

if ((nosegear_is_up == MADE) && (leftgear_is_up == MADE) && (rtgear_is_up == MADE)) {

curr_state = GEAR_UP;

/*Если пилот изменил свое решение, перейти в состояние «опускание шасси»*/

if (gear_lever == DOWN) {

curr_state = LOWERING_GEAR;

void GearUp(void )

/*если пилот передвинул рычаг в положение «вниз»,

Переходим в состояние «опускание шасси»*/

if (gear_lever == DOWN) {

curr_state = LOWERING_GEAR;

void WtgForTakeoff(void )

/* Ожидание перед поднятием шасси.*/

if (timer <= 0.0) {

curr_state = RAISING_GEAR;

/*Если мы снова коснулись или пилот поменял решение – начать все заново*/

if ((squat_switch == DOWN) || (gear_lever == DOWN)) {

curr_state = GEAR_DOWN;

/* Don"t want to require that he toggle the lever again

This was just a bounce.*/

prev_gear_lever = DOWN;

void LoweringGear(void )

if (gear_lever == UP) {

curr_state = RAISING_GEAR;

if ((nosegear_is_down == MADE) && (leftgear_is_down == MADE) &&(rtgear_is_down == MADE)) {

curr_state = GEAR_DOWN;

Во-первых, вы можете заметить, что функциональность каждого состояния реализуется отдельной Си функцией. Конечно, можно было бы реализовать автомат, используя оператор switch с отдельным case `ом для каждого состояния, однако это может привести к очень длинной функции (10-20 строк кода на 1 состояние для каждого из 20-30 состояний). Также это может привести к ошибкам, если будете изменять код на конечных стадиях тестирования. Возможно, вы никогда не забывали оператор break в конце case`a, но со мной такие случаи бывали. Код одного состояния никогда не попадет в код другого, если для каждого состояния у вас будет отдельная функция.

Чтобы избежать применения оператора switch, я использую массив указателей на функции состояний, а переменную, используемую в качестве индекса массива, объявляю типа enum.

Для простоты оборудование ввода-вывода, ответственное за считывание состояния переключателей, включение и выключение насосов и т.д., представлено в виде простых переменных. Предполагается, что данные переменные представляют собой «магические адреса», связанные с оборудованием невидимыми средствами.

Другая очевидная вещь - в этот момент код не играет особой роли. Он просто переходит от одного состояния к другому. Это важный промежуточный этап и его не следует игнорировать. Кстати, было бы неплохо добавить операторы печати, заключенные между директивами условной компиляции (#ifdef DEBUG .. #endif), которые бы выводили текущее состояние и значения входных сигналов.

Залог успеха кроется в коде, который вызывает переход состояний, т.е. определяет, что произошел ввод данных.

Если код правильно проходит через все состояния, следующим этапом становится написание «начинки» кода, то есть именно того, что производит выходной сигнал. Помните, что каждый переход имеет входной сигнал (событие, которое вызвало его) и выходной сигнал (аппаратное устройство ввода-вывода, как в нашем примере). Зачастую это полезно зафиксировать в виде таблицы перехода состояний.

В таблице перехода состояний одна строка приходится на один переход состояния.

При кодировании конечного автомата старайтесь сохранить его силу – ярко выраженное соответствие между требованиями заказчика и кодом. Вероятно, придется скрыть подробности касательно оборудования в другом уровне функций, например, для того, чтобы код конечного автомата максимально походил на таблицу перехода состояний и диаграмму перехода состояний. Подобная симметрия помогает предотвратить ошибки и объясняет то, почему конечные автоматы являются такой важной частью арсенала программиста встраиваемых систем. Конечно, вы могли бы добиться того же эффекта путем установки флажков и бесконечного множества вложенных операторов if, однако при этом будет очень сложно отслеживать код и сравнивать его с пожеланиями заказчика.

Фрагмент кода в листинге 2 расширяет функцию RaisingGear(). Обратите внимание, что код для функции RaisingGear() стремится к зеркальному отображению 2-х рядов таблицы переходов для состояния Raising Gear.

void RaisingGear(void )

/*После того, как все переключатели подняты, переходим в состояние «шасси поднято»*/

if ((nosegear_is_up == MADE) && (leftgear_is_up == MADE) && (rtgear_is_up == MADE)) {

pump_motor = OFF;

gear_lights = EXTINGUISH;

curr_state = GEAR_UP;

/*Если пилот передумал, начать втягивание шасси*/

if (gear_lever == DOWN) {

pump_direction = DOWN;

curr_state = GEAR_LOWERING;

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

В качестве тренировки вы можете расширить конечный автомат, который мы только что рассмотрели, добавив таймаут к циклу втягивания или выдвижения шасси, т.к. инженер-механик не хочет, чтобы гидравлический насос работал дольше 60 секунд. Если цикл заканчивается, пилот должен быть предупрежден переключением зеленой и красной лампочки, и он должен иметь возможность снова переместить рычаг, чтобы повторить попытку. Также вы можете спросить гипотетического инженера-механика, как сказывается на насосе изменение направления на противоположное во время его работы, потому что это происходит в 2-ух случаях, когда пилот передумывает. Конечно, механик скажет, что негативно. Тогда как бы вы изменили конечный автомат, чтобы быстро остановить насос при изменении направления?

Тестирование конечного автомата

Прелесть кодирования алгоритмов в виде конечных автоматов состоит в том, что план проведения теста почти автоматически пишется сам. Все, что вам нужно сделать – это пройти через каждый переход состояния. Я обычно делаю это с маркером в руках, вычеркивая стрелки на диаграмме перехода состояний по мере того, как они успешно проходят тест. Это хороший способ избежать «скрытых состояний» - в тестах они упускаются чаще, чем конкретные состояния.

Это требует значительного терпения и большого количества кофе, так как даже конечный автомат средних размеров может иметь до 100 различных переходов. Кстати, количество переходов – это отличный способ измерить сложность системы. Последнее определяется требованиями заказчика, а конечный автомат делает очевидными объемы тестирования. При менее организованном подходе объем требуемого тестирования может оказаться таким же внушительным, но вы об этом просто не узнаете.

Очень удобно использовать в коде операторы печати, выводящие текущее состояние, значения входных и выходных сигналов. Это позволяет вам с легкостью наблюдать то, что выражает «Золотое Правило Тестирования Программ»: проверяйте, что программа выполняет задуманное, а также то, что она не делает ничего лишнего. Другими словами, получаете ли вы только те выходные сигналы, которые вы ожидаете, и что еще происходит помимо этого? Нет ли «затруднительных» переходов состояний, т.е. состояний, которые случайно проходят, только для одного повтора цикла? Меняются ли выходные сигналы, когда вы этого не ожидаете? В идеале выходные сигналы ваших printfs должны заметно напоминать таблицу перехода состояний.

Наконец - и это касается любого встроенного ПО, а не только ПО, основанного на конечных автоматах - будьте очень осторожны при первом запуске ПО на реальном оборудовании. Очень легко ошибиться с полярностью сигналов – «Ой, я думал, что «1» означает поднять шасси, а «0» - опустить его». Во многих случаях, мой помощник по оборудованию применял временный «куриный переключатель» для защиты ценных компонентов, пока не был уверен, что мое ПО перемещает предметы в правильном направлении.

Запуск

Когда все требования заказчика выполнены, я могу запускать конечный автомат подобной сложности через пару дней. Практически всегда автоматы выполняют то, что я хочу. Самое сложное – это, конечно, точно понять, чего хочет заказчик и убедиться, что заказчик сам знает, чего он хочет. Последнее занимает значительно больше времени!

Мартин Гомез – программист Лаборатории Прикладной Физики при Университете Джона Хопкинса. Занимается разработкой ПО для обеспечения полетов исследовательских космических кораблей. Проработал в области разработки встраиваемых систем в течение 17 лет. Мартин – бакалавр наук в области аэрокосмического инжиниринга и магистр в области электроинжиниринга (университет Корнелл).

Теория автоматов

Определение автомата и его разновидности. Таблицы и графы переходов и выходов. Подавтоматы. Теорема о приведенном автомате

Операции с автоматами

Преобразование автомата Мили в автомат Мура и автомата Мура в автомат Мили. Эквивалентность автоматов. Различимость состояний автоматов. Минимизация автоматов. Синтез автоматов. Распознающие автоматы

Автомат - система механизмов, устройств, в которой полностью автоматизированы процессы получения, преобразования, передачи энергии, материалов, информации Термин "автомат" используется в двух аспектах:

1) техническом,

2) математическом.

При математическом подходе под автоматом понимается математическая модель технического устройства, у которого должны быть входы, внутренние состояния и выходы. Относительно деталей структуры устройства сведений не должно быть.

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

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

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

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

Работа ЦА осуществляется в автоматном времени, определяемом числом периодов поступления входных сигналов.

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

Слова входного языка можно представить символами множества X={x 1 ,x 2 ,...x n }, который называют входным алфавитом , а слова выходного языка - символами множества Y={y 1 ,y 2 ,...y p }, который называют выходным алфавитом . Множество состояний автомата S={s 1 ,s 2 ,...s m } называют алфавитом состояний .


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

Работу абстрактного автомата следует рассматривать применительно к конкретным интервалам времени, т.к. каждому интервалу дискретности t будет соответствовать свой выходной сигнал y(t). Следовательно, функционирование автомата рассматривается через дискретные интервалы времени конечной продолжительности. В абстрактной теории цифровых автоматов считается, что входные сигналы воздействуют на синхронный автомат в момент начала каждого i -того интервала (кванта) времени, выделенного соответствующим синхроимпульсом (тактом), а изменение внутренних состояний автомата происходит в интервалы времени между смежными синхроимпульсами, когда нет воздействия входных сигналов.

Понятие «состояние» используют для того, чтобы установить функциональную зависимость генерируемых автоматом символов и/или слов выходного языка от символов и/или слов входного языка при реализации автоматом заданного алгоритма. Для каждого состояния автомата sÎS и для каждого символа xÎX в момент дискретного времени [t] на выходе устройства генерируется символ yÎY. Эту зависимость определяет функция выходов автомата j. Для каждого текущего состояния автомата sÎS и для каждого символа xÎX в момент дискретного времени [t] автомат переходит в очередное состояние sÎS. Эту зависимость определяет функция переходов автомата y. Функционирование автомата состоит в порождении двух последовательностей: последовательности очередных состояний автомата (s 1[ s 2 s 3 ...) и последовательности выходных символов (y 1 y 2 y 3 ...), которые для последовательности символов (x 1 x 2 x 3 ...) разворачиваются в моменты дискретного времени t = 1,2,3,.... В прямоугольных скобках указывают моменты дискретного времени, которые называют иначе тактами, в круглых скобках - последовательности символов алфавитов X, Y и S.

Итак, математическая модель конечного автомата есть трехосновная алгебра, носителями которой являются три множества X, Y и S, а операциями - две функции j и y.

В статье рассмотрены простые конечные автоматы и их реализация на C++ с помощью switch-конструкций, таблиц времени исполнения и библиотеки Boost Statechart .

Введение

Грубо говоря, конечный автомат (Finite State Machine), глазами пользователя – это черный ящик, в который можно что-то передать и что-то оттуда получить. Это очень удобная абстракция, которая позволяет скрыть сложный алгоритм, кроме того конечные автоматы очень эффективны.

Конечные автоматы изображают в виде диаграмм состоящих из состояний и переходов. Поясню на простом примере:

Как вы наверное догадались – это диаграмма состояний лампочки. Начальное состояние обозначается черным кружком, переходы стрелками, некоторые стрелки подписаны – это события после которых автомат переходит в другое состояние. Итак, сразу из начального состояния, мы попадаем в состояние Light Off – лампа не горит. Если нажать кнопку, то автомат изменит свое состояние и перейдет по стрелке помеченной Push Button , в состояние Light On – лампа горит. Перейти из этого состояния можно опять же по стрелке, после нажатия кнопки, в состояние Light Off .

Также широко используются таблицы переходов:

Практическое применение автоматов

Конечные автоматы широко используются в программировании. Например очень удобно представить работу устройства в виде автомата. Это сделает код проще и позволит легко с ним экспериментировать и поддерживать.

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

Для примера мы реализуем автомат для подсчета чисел и слов в тексте. Для начала договоримся, что числом будет считаться последовательность цифр от 0 до 9 произвольной длины, окруженная пробельными символами (пробел, табуляция, перевод строки). Словом будет считаться последовательность произвольной длины состоящая из букв и также окруженная пробельными символами.

Рассмотрим диаграмму:

Из начального состояния мы попадаем в состояние Start . Проверяем текущий символ, и если он является буквой, то переходим в состояние Word по стрелке помеченной как Letter . Это состояние предполагает, что в данный момент мы рассматриваем слово, анализ дальнейших символов, либо подтвердит данное предположение, либо опровергнет. Итак, рассматриваем следующий символ, если он является буквой, то состояние не изменяется (обратите внимание на круговую стрелку помеченную как Letter ). Если символ не является буквой, а соответствует пробельному символу, то это означает, что предположение было верным и мы обнаружили слово (делаем переход по стрелке Space в состояние Start ). Если символ не является, ни буквой, ни пробелом, значит мы ошиблись в предположении и последовательность которую мы рассматриваем, не является словом (переходим по стрелке Unknown в состояние Skip ).

В состоянии Skip мы находимся до тех пор, пока не будет встречен пробельный символ. После того как пробел был обнаружен мы переходим по стрелке Space в состояние Start . Это нужно, чтобы полностью пропустить строку не соответствующую нашему шаблону поиска.

После перехода в состояние Start , поиск цикл повторяется с начала. Ветвь с распознаванием чисел аналогична ветви распознавания слов.

Автомат с использованием switch-инструкций

Первое – возможные состояния:

После чего мы итерируем по строке подсовывая автомату текущий символ. Сам автомат представляет собой инструкцию switch сначала выполняющей переход в секцию текущего состояния. Внутри секции есть конструкция if-else, которая в зависимости от события (пришедшего символа) изменяет текущее состояние:

const size_t length = text.length () ; for (size_t i = 0 ; i ! = length; ++ i) { const char current = text[ i] ; switch (state) { case State_Start: if (std:: isdigit (current) ) { state = State_Number; } else if (std:: isalpha (current) ) { state = State_Word; } break ; case State_Number: if (std:: isspace (current) ) {

Здесь смотрим на диаграмму – текущее состояние Number , событие Space (встречен пробельный символ), а значит найдено число:

FoundNumber() ; state = State_Start; } else if (! std:: isdigit (current) ) { state = State_Skip; } break ; case State_Word: if (std:: isspace (current) ) { FoundWord() ; state = State_Start; } else if (! std:: isalpha (current) ) { state = State_Skip; } break ; case State_Skip: if (std:: isspace (current) ) { state = State_Start; } break ; } }

Итог:

Высокая эффективность

Простота реализации для простых алгоритмов

— Тяжело поддерживать

Интерпретация времени выполнения

Идея данного подхода проста – надо создать таблицу переходов, заполнить ее, и потом, при наступлении события, найди в таблице следующее состояние и сделать переход:

enum Events { Event_Space, Event_Digit, Event_Letter, Event_Unknown } ; void ProcessEvent(Events event) ; ... struct Transition { States BaseState_; Events Event_; States TargetState_; } ; void AddTransition(States fromState, Events event, States toState) ; ... typedef std:: vector < transition> TransitionTable; TransitionTable Transitions_; States CurrentState_;

Заполнение таблицы:

AddTransition(State_Start, Event_Digit, State_Number) ; AddTransition(State_Start, Event_Letter, State_Word) ; AddTransition(State_Number, Event_Space, State_Start) ; AddTransition(State_Number, Event_Letter, State_Skip) ; AddTransition(State_Number, Event_Unknown, State_Skip) ; AddTransition(State_Word, Event_Space, State_Start) ; AddTransition(State_Word, Event_Digit, State_Skip) ; AddTransition(State_Word, Event_Unknown, State_Skip) ; AddTransition(State_Skip, Event_Space, State_Start) ;

Получилось довольно наглядно. Платой за наглядность будет более медленная работа автомата, что впрочем, часто не имеет значения.

Чтобы автомат, при наступлении некоторых событий, мог оповестить некоторый код, можно добавить в структуру с информацией о переходе (Transition ) указатель на функцию (Action ), которая будет вызываться:

typedef void (DynamicMachine:: * Action) () ; struct Transition { States BaseState_; Events Event_; States TargetState_; Action Action_; } ; ... void AddTransition(States fromState, Events event, States toState, Action action) ; ... AddTransition (State_Number, Event_Space, State_Start, & DynamicMachine:: FoundNumber ) ;

Итог:

Гибкость и наглядность

Проще поддерживать

— Меньшая производительность по сравнению с switch-блоками

Интерпретация времени исполнения. Оптимизация по скорости

Можно ли объединить наглядность со скоростью? Сделать автомат настолько же эффективным, как автомат на switch-блоках вряд ли удастся, но сократить разрыв можно. Для этого надо из таблицы, построить матрицу, такую, чтобы для получения информации о переходе не выполнять поиск, а сделать выборку по номеру состояния и события:

Достигается результат, за счет расхода памяти.

Итог:

Гибкость, наглядность

Эффективен

— Расход памяти (скорее всего несущественно)

Boost Statechart

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

Итак, сначала определим события:

namespace Events { struct Digit : boost:: statechart :: event < Digit> { } ; struct Letter : boost:: statechart :: event < Letter> { } ; struct Space : boost:: statechart :: event < Space> { } ; struct Unknown : boost:: statechart :: event < Unknown> { } ; }

Саму машину (обратите внимание, что второй параметр шаблона – начальное состояние):

struct Machine : boost:: statechart :: state_machine < Machine, States:: Start > { } ;

И собственно сами состояния. Внутри состояний требуется определить тип описывающий переходы (reactions ), а если переходов несколько, то перечислить их в списке типов boost::mpl::list . Второй параметр шаблона simple_state – тип описывающий машину. Переходы описываются параметрами шаблона transition, парой Событие — Следующее состояние :

namespace States { struct Start : boost:: statechart :: simple_state < Start, Machine> < boost:: statechart :: transition < Events:: Digit , States:: Number >< Events:: Letter , States:: Word > > reactions; } ; struct Number : boost:: statechart :: simple_state < Number, Machine> { typedef boost:: mpl :: list < boost:: statechart :: transition < Events:: Space , States:: Start > , boost:: statechart :: transition < Events:: Letter , States:: Skip > , boost:: statechart :: transition < Events:: Unknown , States:: Skip > > reactions; } ; struct Word : boost:: statechart :: simple_state < Word, Machine> { typedef boost:: mpl :: list < boost:: statechart :: transition < Events:: Space , States:: Start > , boost:: statechart :: transition < Events:: Digit , States:: Skip > , boost:: statechart :: transition < Events:: Unknown , States:: Skip > > reactions; } ; struct Skip : boost:: statechart :: simple_state < Skip, Machine> { typedef boost:: statechart :: transition < Events:: Space , States:: Start > reactions; } ; }

Машина построена, осталось только инициализировать ее и можно использовать:

Machine machine; machine.initiate () ; ... machine .process_event (Events:: Space () ) ;

Итог:

Гибкость, расширяемость

— Эффективность

Быстродействие

Я написал тестовую программу для проверки быстродействия построенных автоматов. Через автоматы я прогнал текст размером ~17 Мб. Вот результаты прогона:

Loading text Text length: 17605548 bytes 0.19 s Running BoostMachine Words: 998002, numbers: 6816 0.73 s Running DynamicMachine Words: 998002, numbers: 6816 0.56 s Running FastDynamicMachine Words: 998002, numbers: 6816 0.29 s Running SimpleMachine Words: 998002, numbers: 6816 0.20 s

Что осталось не рассмотренным

Неохваченными остались еще несколько реализаций конечных автоматов (рекомендую http://www.rsdn.ru/article/alg/Static_Finite_State_Machine.xml и http://www.rsdn.ru/article/alg/FiniteStateMachine.xml), генераторы строящие автоматы из описаний, библиотека Meta State Machine из Boost версии 1.44.0, а также формальные описания конечных автоматов. Со всем перечисленным любопытный читатель может ознакомиться самостоятельно.