标签:语义分析 原理 tps ## 序列 地址 tchar note const
前面研究OS的经历实在是令人心力憔悴。。所以换个新鲜的,把自己的刷题感悟整理一番。刷了有些题了,就先拿最近几天hard题打头阵吧。首先说的是(065)Valid Number这个题,其实一眼看起来很简单,不就是for/while/if/else吗?那么你可能不知道这道题其实有一个更加简(bian)洁(tai)的方法,听我慢慢道来。
Validate if a given string is numeric.
Some examples: "0" => true " 0.1 " => true "abc" => false "1 a" => false "2e10" => true
Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up front before implementing one.
Java for LeetCode 065 Valid Number:
public boolean isNumber(String s) { s = s.trim(); String[] splitArr = s.split("e"); if (s.length() == 0 || s.charAt(0) == ‘e‘ || s.charAt(s.length() - 1) == ‘e‘ || splitArr.length > 2) return false; for (int k = 0; k < splitArr.length; k++) { String str = splitArr[k]; boolean isDecimal = false; if (str.charAt(0) == ‘-‘ || str.charAt(0) == ‘+‘) str = str.substring(1); if (str.length() == 0) return false; for (int i = 0; i < str.length(); i++) { if (‘0‘ <= str.charAt(i) && str.charAt(i) <= ‘9‘) continue; else if (str.charAt(i) == ‘.‘ && !isDecimal) { if (k == 0 && str.length() > 1) isDecimal = true; else return false; } else return false; } } return true; }
只要会点C++,那么常规解法就不在话下,其实就是手动实现一遍itoa而已。
常规解法的优点是:门槛低/常人写得出/容易修改,也就是定制性好/扩展性差。
同时,它的缺点是:一旦验证逻辑变复杂,那就gg了。比如我想把复数也算进去啊,那又得改那堆杂七杂八的代码,令人感觉不会再爱了。
正则表达式30分钟入门教程总结得比较好。简单来说,正则表达式(regex)可以表示一个特定的词法(编译原理之词法分析、语法分析、语义分析 - nic_r的专栏 - 博客频道 - CSDN.NET),如整数、实数、复数、邮箱地址、电话号码等。regex除了有匹配的功能之外,它还带有替换/解析功能,这样,能够满足涉及字符串操作的大多数需求。
比如,匹配方面,涉及用户名匹配、邮箱地址的匹配等,如果这时你用常规解法就太臃肿、太麻烦了。替换/解析方面,如解析HTML/XML/JSON等,比如便捷。
那么正则表达式与常规解法有什么不同呢?
刚才提到,常规解法虽然容易修改,但它的扩展性不足,我想更改一点需求,就要大刀阔斧改代码,令人不会再爱。那么如何解决这个扩展性的问题呢?那就需要将算法给抽象出来。
如果单用if/else/while/for做一个邮箱匹配,这时又需要做一个数字匹配功能,那么这两种代码是八竿子打不着的,根本没法子复用代码啊,怎么办呢?
其实稍微用脑子想一想——你写的爬虫程序和他写的游戏程序也是风马牛不相及吧?但是编译器将它们翻译成汇编语言后,是不是又有共同点了?比如都有Jump跳转啊,有mov啊,相似度瞬间提高。这里面的原理是什么呢?原来杂七杂八的代码间,通过编译器的翻译,竟然变成了两份差不多的汇编代码(指用的指令大体相似)。那么方法是翻译吗?
也就是说,原始的两种内容不同的代码,可能甲有着C++的高级特性,乙又是C写的,它们翻译成汇编后,用到的指令有99%都是相同的。反过来,如果我以汇编语言为标准,来表示甲和乙,那么这时候两者的代码有99%是相似的。这时,我们发现了可重用性!
回过头来,想一想,假如有一种语言a可以表达数字、邮箱地址,那我们就不需要再写不同的C/C++代码了,即:有一种机制将你的语言a的表达式翻译成对应的代码,运行这个代码,可以完成匹配工作。这不就是编译器干的事么?
啰嗦了那么多,其实意思就是:想要增加两种功能不同的代码之间的相似程度,必须从代码中的相同点/不同点抽象出一种崭新的语言,用这种崭新的语言可以以统一的语法形式来表达这两份代码。
而正则表达式,正是一种崭新的语言。涉及正则表达式的语法、使用、解析,及NFA、DFA等知识这里不再赘述,请参阅专业书籍或是一些博客。
大致步骤是:
还好自己的https://github.com/bajdcc/jMinilang中有生成DFA的代码。
正则匹配部分在priv.bajdcc.util.lexer.test.TestRegex,直接运行它,然后输入上述正则表达式,那么具体信息就出来了。
详细信息(程序自动生成):
#### 正则表达式语法树 #### 序列 { 循环{0,-1} { 字符 [\u0020,‘ ‘] } 循环{0,1} { 字符 [\u002b,‘+‘],[\u002d,‘-‘] } 分支 { 序列 { 循环{0,-1} { 字符 [\u0030,‘0‘]-[\u0039,‘9‘] } 循环{0,1} { 字符 [\u002e,‘.‘] } 循环{1,-1} { 字符 [\u0030,‘0‘]-[\u0039,‘9‘] } } 序列 { 循环{1,-1} { 字符 [\u0030,‘0‘]-[\u0039,‘9‘] } 循环{0,1} { 字符 [\u002e,‘.‘] } 循环{0,-1} { 字符 [\u0030,‘0‘]-[\u0039,‘9‘] } } } 循环{0,1} { 序列 { 字符 [\u0065,‘e‘] 循环{0,1} { 字符 [\u002b,‘+‘],[\u002d,‘-‘] } 循环{1,-1} { 字符 [\u0030,‘0‘]-[\u0039,‘9‘] } } } 循环{0,-1} { 字符 [\u0020,‘ ‘] } } #### 状态集合 #### [\u0020,‘ ‘] [\u002b,‘+‘] [\u002d,‘-‘] [\u002e,‘.‘] [\u0030,‘0‘]-[\u0039,‘9‘] [\u0065,‘e‘] #### 最小化 #### 状态[0] => 0, 边 => [1] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 边 => [0] 类型 => 字符区间 [\u0020,‘ ‘] 边 => [2] 类型 => 字符区间 [\u002e,‘.‘] 边 => [3] 类型 => 字符区间 [\u002b,‘+‘] 边 => [3] 类型 => 字符区间 [\u002d,‘-‘] 状态[1][结束] => 3,4,6, 边 => [4] 类型 => 字符区间 [\u002e,‘.‘] 边 => [5] 类型 => 字符区间 [\u0065,‘e‘] 边 => [6] 类型 => 字符区间 [\u0020,‘ ‘] 边 => [1] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 状态[2] => 5, 边 => [7] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 状态[3] => 2, 边 => [2] 类型 => 字符区间 [\u002e,‘.‘] 边 => [1] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 状态[4][结束] => 5,8, 边 => [5] 类型 => 字符区间 [\u0065,‘e‘] 边 => [6] 类型 => 字符区间 [\u0020,‘ ‘] 边 => [4] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 状态[5] => 10, 边 => [8] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 边 => [9] 类型 => 字符区间 [\u002b,‘+‘] 边 => [9] 类型 => 字符区间 [\u002d,‘-‘] 状态[6][结束] => 11, 边 => [6] 类型 => 字符区间 [\u0020,‘ ‘] 状态[7][结束] => 6, 边 => [5] 类型 => 字符区间 [\u0065,‘e‘] 边 => [7] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 边 => [6] 类型 => 字符区间 [\u0020,‘ ‘] 状态[8][结束] => 14, 边 => [6] 类型 => 字符区间 [\u0020,‘ ‘] 边 => [8] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] 状态[9] => 13, 边 => [8] 类型 => 字符区间 [\u0030,‘0‘]-[\u0039,‘9‘] #### 状态转移矩阵 #### 0 3 3 2 1 -1 6 -1 -1 4 1 5 -1 -1 -1 -1 7 -1 -1 -1 -1 2 1 -1 6 -1 -1 -1 4 5 -1 9 9 -1 8 -1 6 -1 -1 -1 -1 -1 6 -1 -1 -1 7 5 6 -1 -1 -1 8 -1 -1 -1 -1 -1 8 -1
class Solution { inline int getCharMap(const char& c) { switch (c) { case ‘ ‘: return 0; case ‘+‘: return 1; case ‘-‘: return 2; case ‘.‘: return 3; case ‘0‘: case ‘1‘: case ‘2‘: case ‘3‘: case ‘4‘: case ‘5‘: case ‘6‘: case ‘7‘: case ‘8‘: case ‘9‘: return 4; case ‘e‘: return 5; } return -1; } public: bool isNumber(string s) { using t = int(*)[6]; int mm[] = { 0 ,3 ,3 ,2 ,1 ,-1, 6 ,-1 ,-1 ,4 ,1 ,5, -1 ,-1 ,-1 ,-1 ,7 ,-1, -1 ,-1 ,-1 ,2 ,1 ,-1, 6 ,-1 ,-1 ,-1 ,4 ,5, -1 ,9 ,9 ,-1 ,8 ,-1, 6 ,-1 ,-1 ,-1 ,-1 ,-1, 6 ,-1 ,-1 ,-1 ,7 ,5, 6 ,-1 ,-1 ,-1 ,8 ,-1, -1 ,-1 ,-1 ,-1 ,8 ,-1, }; auto m = (t)mm; bool final[] = {0, 1, 0, 0, 1, 0, 1, 1, 1, 0}; int status = 0; auto c = s.c_str(); for (;;) { auto local = *c++; int charClass = getCharMap(local); int refer = -1; if (charClass != -1) { refer = m[status][charClass]; } if (refer == -1) { return local == 0 && final[status]; } else { status = refer; } } } };
标签:语义分析 原理 tps ## 序列 地址 tchar note const
原文地址:https://www.cnblogs.com/bajdcc/p/8972954.html