摘自 维基百科 自动机编程
自动机编程(
英语:Automata-based programming)是
编程范型中的一种,是指程序或其中的部份是以
有限状态机(FSM)为模型的程序,有些程序则会用其他型式(也更复杂)的
自动机为其模型。
有限状态机编程(
英语:FSM-based programming)大致上等同于自动机编程,但有限状态机编程专指以有限状态机为模型的程序。
自动机编程有以下的二项特征:
- 程序运行的时间中可以清楚划分成数个自动机的步骤(step),每一个步骤即为一个程序区段,有单一的进入点,可以是一个函数或其他程序。若有需要时,程序区段可以再依其状态的不同,划分为子区段。
- 不同步骤的程序区段只能通过一组清楚标示的变量交换信息,这些变量称为状态(state),使用自动机编程的程序不能用其他不显然可见的方式标示状态,例如区域变量的数值、回传地址、目前程序指针的位置等。因此一程序在任二个不同时间下的差异,只有状态数值的不同,其余都相同。
自动机编程的运行过程是一个由自动机步骤形成的循环。
自动机编程中处理问题的思考方式很类似在利用
图灵机、
马尔可夫算法处理问题时的思考方式。
示例
考虑一个C语言的程序,由标准输入流一行一行的读取数据,打印各一行的第一个英文单字。因此一开始需确认第一个英文单字之前是否有空白,若有,需读取所有空白后略过不打印,读取第一个英文单字然后打印,之后读取其他内容略过不打印,直到读到换行符号为止。任何情形下只要读到换行符号,就重新开始此算法,任何情形下只要读到文件退出(end-of-file)的符号,就退出程序。
传统C语言的程序
以下是传统命令式编程的C语言程序:
#include
int main(void)
{
int c;
do {
c = getchar();
while(c == ' ')
c = getchar();
while(c != EOF && c != ' ' && c != '
') {
putchar(c);
c = getchar();
}
putchar('
');
while(c != EOF && c != '
')
c = getchar();
} while(c != EOF);
return 0;
}
自动机编程的程序
上述问题也可以用有有限状态机的方式处理,此程序有三个不同的阶段:读取并跳过第一个单字前的空白、读取第一个单字并且打印、跳过后续的所有字符。以下将这三个阶段定义为三个状态before
、inside
及after
。自动机编程的程序如下:
#include
int main(void)
{
enum states {
before, inside, after
} state;
int c;
state = before;
while((c = getchar()) != EOF) {
switch(state) {
case before:
if(c == '
') {
putchar('
');
} else
if(c != ' ') {
putchar(c);
state = inside;
}
break;
case inside:
switch(c) {
case ' ': state = after; break;
case '
':
putchar('
');
state = before;
break;
default: putchar(c);
}
break;
case after:
if(c == '
') {
putchar('
');
state = before;
}
}
}
return 0;
}
虽然此程序较长,至少有一个明显的好处,程序中只调用一个读取字符的getchar()函数,而且程序中只有一个循环,不像之前程序使用四个循环。
此程序中
while
循环内的程序即为自动机的步骤,而循环本身即可重复的运行自动机的程序。
此程序实现如右图所示的有限状态机,其中
N表示换行字符、
S表示空白、
A表示其他的字符。自动机依目前状态及读取的字符不同,会运行图中一个箭头所示的动作,可能是由一个状态跳到下一个状态,也者停在原来的状态。其中有些箭头有标示星号,表示需打印读到的字符。
自动机编程中,不一定要为每一个状态撰写独立的处理程序,而且有时状态是由许多变量组成,无法针对每一个状态规划个别的处理程序。此想法有时有助于程序的精简,例如在上述程序中,不论是在哪一个状态,针对换行字符的处理都一様,因此程序可以先处理换行字符,其他输入字符时才依不同状态进行处理,简化后变成以下的程序:
#include
int main(void)
{
enum states {
before, inside, after
} state;
int c;
state = before;
while((c = getchar()) != EOF) {
if(c == '
') {
putchar('
');
state = before;
} else
switch(state) {
case before:
if(c != ' ') {
putchar(c);
state = inside;
}
break;
case inside:
if(c == ' ') {
state = after;
} else {
putchar(c);
}
break;
case after:
break;
}
}
return 0;
}
独立的自动机步骤程序
上述程序的一个重要特点是自动机步骤的程序区块都只使用区域变量,以下的例子将自动机步骤集成为一个独立的函数step()
,更可以突显上述的特点:
#include
enum states { before, inside, after };
void step(enum states *state, int c)
{
if(c == '
') {
putchar('
');
*state = before;
} else
switch(*state) {
case before:
if(c != ' ') {
putchar(c);
*state = inside;
}
break;
case inside:
if(c == ' ') {
*state = after;
} else {
putchar(c);
}
break;
case after:
break;
}
}
int main(void)
{
int c;
enum states state = before;
while((c = getchar()) != EOF) {
step(&state, c);
}
return 0;
}
此例清楚的呈现自动机编程程序的基本特点:
- 各自动机步骤程序的运行时间不互相重叠。
- 前一个步骤和下一个步骤之间所交换的数据只有标示为“自动机状态”的变量(此例中为变量state)。
显式的状态转换表
自动机编程可以用显式的
状态转换表来表示。以下的程序中的
the_table
数组即为状态转换表,其列表示三个不同的状态,其每一栏对应输入的字符(从左到右分别是空白、换行字符及其他字符)。
对于每一种可能的状态及输入字符的组合,表中有其对应的新状态及一个决定是否否显示输入字符的旗标。在实务的专案中状态转换表可能更为复杂,例如可能包括所有可能条件组合下需调用的函数指针。
#include
enum states { before = 0, inside = 1, after = 2 };
struct branch {
unsigned char new_state:2;
unsigned char should_putchar:1;
};
struct branch the_table[3][3] = {
/* ' ' '
' others */
/* before */ { {before,0}, {before,1}, {inside,1} },
/* inside */ { {after, 0}, {before,1}, {inside,1} },
/* after */ { {after, 0}, {before,1}, {after, 0} }
};
void step(enum states *state, int c)
{
int idx2 = (c == ' ') ? 0 : (c == '
') ? 1 : 2;
struct branch *b = & the_table[*state][idx2];
*state = (enum states)(b->new_state);
if(b->should_putchar) putchar(c);
}
int main(void)
{
int c;
enum states state = before;
while((c = getchar()) != EOF)
step(&state, c);
return 0;
}