json的主页上,提供了number类型的符号识别过程,如下:
图片引用:http://www.json.org/json-zh.html
实际上这张图片表示的是一个状态机,只是状态没有标出来。因为这个状态机上存在ε转换,所以它是一个NFA(不确定有限自动机)。ε转换也即不需要输入串就能进行的转换,例如从开始状态到0之前的状态。而我们进行识别的时候,使用DFA(确定有穷自动机)会简单方便得多。所以首先应该将这个NFA转成DFA。
首先把这个NFA规范一下,写成状态与箭头的形式:
NFA转DFA最常用的方法是子集法,不过由于这个状态机的字符类型比较多,使用表格方式会使得表格很大并且很稀疏。这里用简便的记法,直接从左至右进行确定化:
考虑初始状态0,ε-closure(0)={0,1}
,就直接简记为{0,1}状态,写出它相邻的状态,如果相邻状态包含ε,则做同样的处理:
然后再选定{2,6,10},写出它的相邻状态:
用同样的方式,写出1、{2,3,6,10}的相邻状态,得到状态4和{7,8},这里需要注意的是1跟{0,1}是两个不同的状态。并且{2,3,6,10}是包含{2,6,10}的,因此可以利用之前{2,6,10}的结果来简化运算,所以只需要考虑3的相邻状态,有点像动态规划思想。重复以上步骤,最终得到一个不含ε的DFA:
得到这个DFA之后并不一定是最简的,我们可以对它进行简化。首先为了方便,对它的状态都用字母替代吧:
简化的主要思路就是将状态的集合不断划分成子集。划分的办法是用一个集合相关的符号去测试这个集合中的状态,如果发现某个状态测试结果与其他状态不同,则划分状态,如果无法区分,则放到同一个集合中。
比如上述的DFA,首先可以肯定的是所有状态可以划分成“非终止状态”和“终止状态”两个集合,因为非终止状态总要转换到终止状态的。由于状态机是从左至右写出的,所以通常情况下,只需要考虑相邻的状态是否等价。并且,如果把这个状态机写成状态转换表,表项是很稀疏的,所以实际上可以合并的状态很少。具体过程如下:
对于终结符{A,C,F,G}每两个都互不等价,因此划分成四个状态{A},{C},{F},{G}
对于非终结符{S,B,D,E,G},E和G不等价,原因在于E对于+/-结果为G,而G不能通过+/-,并且E,G可以通过digit转换到H,而其他都不能这样转换,所以原集合可以划分成{S,B,D},{E},{G}
{S,B}和{D}很明显是不等价的,而对于S和B,唯一的区别就是S能够通过-转换到B,而B不能通过-
任何状态都不能合并,所以上述的状态机已经不能再简化。
正规式把上面的状态机写出来就是(非通常的正则表达式语法):
1
2
3
4
|
A=0|-0
C=([1-9]|-[1-9])d*
F=(A|C).dd*
H=(A|C|F)(e|E)(d|(+|-)d)d*
|
有了以上的状态机,我们就可以实现一个number识别程序了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
#include <assert.h>
#include <ctype.h>
#include <iostream>
enum STATE{ S, A, B, C, D, E, F, G, H };
const char* RecognizeNumber(const char* str, STATE* last_state)
{
STATE state = S;
*last_state = S;
const char *p = str;
for (; *p != '\0'; p++){
const char& ch = *p;
switch (state){
case S:
if (ch == '0') state = A;
else if (ch == '-') state = B;
else if (ch >= '1' && ch <= '9')state = C;
else return p;
break;
case A:
if (ch == 'e' || ch == 'E') state = E;
else if (ch == '.') state = D;
else return p;
break;
case B:
if (ch == '0') state = A;
else if (ch >= '1' && ch <= '9') state = C;
else return p;
break;
case C:
if (isdigit(ch)) state = C;
else if (ch == '.') state = D;
else if (ch == 'e' || ch == 'E') state = E;
else return p;
break;
case D:
if (isdigit(ch)) state = F;
else return p;
break;
case E:
if (isdigit(ch)) state = H;
else if (ch == '+' || ch == '-') state = G;
else return p;
break;
case F:
if (isdigit(ch)) state = F;
else if (ch == 'e' || ch == 'E') state = E;
else return p;
break;
case G:
if (isdigit(ch)) state = H;
else return p;
break;
case H:
if (isdigit(ch)) state = H;
else return p;
break;
default:
assert(true);
}
*last_state = state;
}
return p;
}
void Recognize(const char* s)
{
STATE state;
const char* endp = RecognizeNumber(s, &state);
bool onterminal = (state == A || state == C || state == F || state == H);
std::cout << s << (*endp == '\0' && onterminal ? "\tYes" : "\tNo") << std::endl;
}
int main()
{
Recognize("0");
Recognize("-1");
Recognize("23");
Recognize("-3.4");
Recognize("5e6");
Recognize("5.3E+7");
Recognize("-");
Recognize("-0.4e");
Recognize("-0.");
Recognize("3.6a");
Recognize("-3.6ea");
Recognize("053ab4");
Recognize("01");
Recognize("00.01");
return 0;
}
|