提示:通过有限状态机实现的简单单词统计功能
有限状态机简介
对于大学学过数电的同学,状态机理解可能比较简单,因为会需要画状态转换图,这里只会以一个简单的例子来抛转引玉,对有限状态机感兴趣的可自行查阅更多资料来研究。
所谓的状态机,抽象出来,就是一个状态转换图,用有限状态机的思维来处理问题,最关键的在于:
(1)该问题/模型能抽象成几个状态;
(2)什么条件满足时会由一个状态转换到另一个状态
(3)应初始化为什么状态
单词统计状态抽象
以我们的主题为例,统计一篇文章中的单词个数,假设文章内如如下:
hello, we are testing a finite-state machine;
well, let’s get start.
我们知道单词一般是由一个或多个连续字母组成的,一个字符只能是组成单词的一部分或非单词的一部分,因此我们将状态分为两个:
单词状态----该字符是组成单词的一部分;非单词状态----该字符不是组成单词的一部分
单词统计状态转换条件
(1)从一个字母到另外一个字母,仍在同一个单词中;
(2)但从一个字母变成一个符号、空格,一个单词结束,从单词状态转换到非单词状态;
(3)从一个符号、空格变成另一个符号、空格,认为仍处于非单词状态;
(4)从一个符号、空格变成一个字母,从非单词状态转化到单词状态;
用状态转换图表示如下:
单词统计初始化状态
解决了状态和状态间的转换关系我们还要解决初始化状态的问题,初始化状态直接决定了一个状态机是否能正常工作,对于有些状态机,自动运行后始终能循环回到初始化状态,但有的状态机不行。这里很简单,我们最开始单词个数肯定是0,因此初始化未非单词状态。
代码实现
通过上述的分析,我们可以实现如下代码:
//words_cal.c
#include <stdio.h>
#define ST_WORD 0 //单词状态
#define ST_SYMB 1 //符号状态
#define INIT ST_SYMB //初始状态
//判断读入的字符是否是符号,是返回1,否返回0
int is_symbol(const char c)
{
if((' ' == c) || ('\n' == c) || ('\t' == c) || ('\"' == c) || ('\'' == c) || ('+' == c) || (',' == c) || (';' == c))
return 1;
return 0;
}
int count_word(const char *filename)
{
int status = INIT;
FILE *fp = fopen(filename, "r");
if(fp == NULL) return -1;
int word_num = 0;
char c;
while(((c = fgetc(fp)) != EOF))
{
//理论上应先判断当前状态,对应处理每个状态内容,可以简化写成如下
if(is_symbol(c))
{
status = ST_SYMB;
}else if(status == ST_SYMB)
{
status = ST_SYMB;
word_num++;
}
}
fclose(fp);
return word_num;
}
//输入要统计的文件路径作为参数
int main(char argc, char *argv[])
{
if(argc < 2) return -2;
printf("word_num: %d\n", count_word(argv[1]));
return 0;
}
进一步优化
我们在实际运行过程中,要考虑到一些特殊情况,如当出现符号"-“时,我们上面是没做处理,即默认是单词的,实际上当遇到”-"时,假如后面紧跟着字母,或紧跟’\n’,新行又紧跟字母,那么我们认为仍为同一个单词,但如果再是这样“–”或“------”,这时我们就应将它视为新的状态了。这里不继续往下完善,感兴趣的刚好可以作为练习。