06 2013 档案
摘要:hopcroft法的复杂度,他们说是nlogn,可是都没有严格的证明。难得找到一篇讲的详细点的论文,却又啰里啰唆的,不过那篇论文里面采用的是颜色树这个结构,有点意思。前面的那个算法是n的平方复杂度,虽然这个复杂度计算都是建立在一些操作为单位时间操作的基础上。可是这些被认为的单位时间操作在我的实现中却有着平方复杂度,情何以堪,万恶的理论计算机科学家。hopcroft实现的代码,太长了,还没写完。不过核心的子集分割已经完成了,剩下的就是分配节点及重新构建邻接表。明天再说吧。 1 #include "dfa_to_dfa.h" 2 pdfa_edge* rev_dfa_table
阅读全文
摘要:上个版本测试的时候,只用了两个非常简单的测试用例,所以好多情况有问题却没有测试出来bug1:在生成diff_matrix的时候,循环变量少循环了一次,导致最后一个节点在如果无法与其他点合并的情况下,程序不会给他生成一个群标号。修改:把循环变量那里加上等于号bug2:在遍历群的时候,程序是以碰到空指针为结束的,但是在malloc内存的时候,系统并不为这个内存初始化为0,而是0xcd,所以以是不是空指针来判断边界是不可行的,会造成错误,导致读取了而外的信息。修改:在遍历群的时候,直接以群的数目来做条件测试bug3:在nfa转dfa的时候,如果某一个dfa节点在某一个字母上的转换导致了目标位图tem
阅读全文
摘要:采取的方法是hopcroft的填表法,详情见如下代码 1 #include "nfa_to_dfa.h" 2 int* dfa_diff_matrix; 3 4 int mini_dfa_number;//这个是最小化的 dfa表的索引 5 typedef struct _min_dfa_node 6 { 7 pdfa_edge begin; 8 int is_end;//记录是否是接受节点 9 }min_dfa_node,*pmin_dfa_node; 10 min_dfa_node mini_dfa_table[100];//设定为100,其实也可以ma...
阅读全文
摘要:为了加速转换的处理,我压缩了符号表。具体算法参考任何一本与编译或者自动机相关的书籍。这里的核心问题是处理传递性闭包,transitive closure,这个我目前采取的是最简单的warshall算法,虽然是4次的复杂度,但是由于我构建nfa的时候并没有采取标准的方法,使得nfa的节点减少很多。ps,上上篇所说的re转nfa,我这里有一个修改,就是对于or转换,不再增加节点,而是只增加两条空转换边。相关代码如下 1 #include "nfa_process.h" 2 //首先在原来的nfa节点中,把最后的那个正则的开始节点可达的那些节点提取出来,相当于又一次的拷贝 3 p
阅读全文
摘要:太累了,感觉不会再爱了。问题已经解决,具体的懒得说了。 1 #include "regular_preprocess.h" 2 //这个版本终于要上nfa了,好兴奋啊 3 //由于连个节点之间可能有多条边,所以只能用邻接表来存储了 4 //注意这里是有向图 5 //对于每一个token,这里都会生成一个或多个图节点 6 //但是每个token会附带另外的两个域,即这个token的开始节点和结束节点 7 //因为内部节点对于外部来说是不可连接的,所以不需要暴露 8 //这里有一个难题,就是空转换如何表示,这里我们必须找一个不可打印字符来代表空转换 9 //楼主自己查了一下as
阅读全文
摘要:正则到nfabug的解决方法前面提到了这个bug,为了解决这个bug,我们必须在每次引用到一个假名的时候,都构建一个拷贝。现在假设我们遇到了一个假名,并得到了他的开始节点和结束节点,当前的难题就是构造这个假名所代表的nfa的副本。构造方法类似于子集构造法,我们设立一个集合,这个集合为R,集合中的每个元素都有一个标志位为访问位。初始化R为开始节点a,并让他的访问位为0。现在开始进入迭代,只要R中存在访问位为0的点,将他的访问位改为1,然后将他的邻接表中的点都加入到R中。加入的时候,考虑R中是否已经存在这个元素,如果已经存在,则不加入。如果不存在,则加入,并设置访问位为0。然后返回迭代判断。最后当
阅读全文
摘要:本人写的一个正则到nfa的bug刚写完前面的那篇,自己用脑子过了一下,发现了一个bug。具体情况如下。这个bug的产生条件是多次调用假名的时候,每次调用都会修改假名的nfa图。直接这么说不好理解,我就拿例子来讲吧。假设我们已经定义了一个假名num,而现在我们有一个正则表达式调用了两次这个假名,nums:[num][num],根据前面那篇文章里面所谈到的方法,会生成如下所示的nfa。这里假设num的开始节点为1,结束节点为2。 但是由于两个节点1和两个节点2引用的是相同的位置,所以上面的图等价于下面的图。可以明显的看出这个并不是我们所需要的规则,图中所表示的是[num]+。之...
阅读全文
摘要:[在此处输入文章标题]正则到nfa前言在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换。可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多的代码。为了简化代码,我实现了我自己的re到nfa的规则。注意我的这套re规则只包括如下几种类型:闭包,即*运算符一个或多个,即+运算符存在或不存在,即?运算符。这三个运算符的优先级最高而且都是单目运算符。括号括起来的,即成对的括号,其实也不算运算符,只能当作分隔符。中括号括起来的,作为假名,也算是分隔符连接运算符,即.运算符,但是在输入re的时候默认不输入这个符号,只有在处理输入的时候才添加这个符号。这个运算符的
阅读全文
摘要:这个版本修改了前面版本的两个个bug。第一个:识别到字符集的时候,只是将name_number加1,却并不对reg_pattern_table[name_number]进行初始化。第二个:识别到假名的时候,并不为他分配一个name_number,而只是在hash表中为其分配一个表项。现在,当识别到这两个的时候,都会为之分配一个name_number,并在reg_pattern_table中正确的初始化。相关的修改的代码都在tackle_particle()函数中。还有对tackle_cat()函数的定义移动到了tackle_invisible_cat()函数的前面。另外一个重大的修改就是,将当
阅读全文
摘要:关于正则的语法方面,就做到这吧。下一步是生成nfa,进而dfa,以及dfa的最简化。等下一个版本出来的话,估计要好久了。 1 #include 2 #include 3 #include 4 //这个版本在上一个版本的基础上,添加了?\+这两个操作符,这两个操作符都是单目操作符,跟*操作符同一级别 5 //因此碰到这两个操作符的时候,跟*操作符一样的处理 6 //除了新加的操作符外,还添加了字符集,模式为a-z这种,但是字符集只能通过假名来引用 7 //因此如果想使用字符集,则必须先通过假名来定义这个字符集,然后在后续的正则表达式中通过假名 8 //来使用这个字符集 9...
阅读全文
摘要:默认为转义字符的优先级最高,而且转义字符的作用是让输入处理函数把转义字符的下一个字符当作字符值看待,而不是可能的操作符。当前版本并不支持c语言中那些特殊含义的转义字符,例如\t。这个特性将会在之后的版本中加上。而对于c语言中的三元组转义字符,我将不会考虑他的存在,没啥意义。下面是代码,欢迎测试。 1 #include 2 #include 3 #include 4 //这个版本允许定义正则子表达式,定义的时候以名字开头,然后是冒号,然后是正则表达式主体。 5 //在引用子表达式的时候,需要用中括号把子表达式括起来,因此中括号也跟其他操作符一样,作为保留字符 6 //这个版本加...
阅读全文
摘要:子表达式的语法为name:regex在处理时,会把name加入进符号表中,方法是hash,最简单的加法而在regex中如果碰到[name]这种模式,则尝试去找符号表,如果找不到则报错。代码如下,欢迎大家测试。#include #include #include //这个版本允许定义正则子表达式,定义的时候以名字开头,然后是冒号,然后是正则表达式主体。//在引用子表达式的时候,需要用中括号把子表达式括起来,因此中括号也跟其他操作符一样,作为保留字符//目前还没有处理转义字符,再过几个版本吧。int token[100];int token_pointer;char reg_operator[10
阅读全文
摘要:由于当前的连接符变为非显示,所以在有些时候需要考虑当前输入指针所指的位置是否缺少连接符,如果缺少,则将连接符入栈。 1 #include 2 #include 3 #include 4 //这里默认是没有显示的连接运算符,运算符的优先级为括号、闭包*、连接.、并| 5 //在括号后及闭包后可能缺少显示的连接符,因此需要考虑添加连接符,而由于并操作符的优先级比连接符低 6 //所以就不需要在处理并操作符的时候去考虑是否缺少显示的连接符了 7 int token[100]; 8 int token_pointer; 9 char reg_operator[100]; 10 ...
阅读全文
摘要:这里只是当作自己的代码版本管理器使用,并不去详细介绍代码,毕竟我的注释里面已经说明了很多。欢迎大家测试,如果发现出错了,望在下面回复,多谢。注意,当前的假定是输入自己不能有错误,而且*、(、)、.、|这几个操作符是保留字,其他的字符则当作其自身意思。当前不考虑转义字符及三元组,以及不考虑子表达式命名,因此只能处理一个长正则表达式。 1 #include 2 #include 3 #include 4 //这里只处理最简单的正则表达式,即包括括号,星号运算,连接运算和分支运算,这里分支运算在压栈是是一个点号 5 //其中括号优先级最大,其次是星号运算其次是连接运算最后是分支运算 ...
阅读全文