一.上下文无关文法的输入与存储
按文法的四元组输入,用list存储,其中每个产生式用tuple保存,然后存在list中,集合可以通过映射关系存储在dict中。
list:可以存储任意类型,同时可以通过下表索引,用来存储终结符和非终结符,以及存储放在元组里的产生式
set:非常方便的实现判重,同时可以实现很多集合运算,轻松实现first、follow和select的存储运算
tuple:数据不能修改,可以通过下表索引,用来存储产生式,同时可以作为dict的键来索引(list不能作为dict的键)
dict:可以存储字符与 first、follow 集合的映射,产生式和select集合的映射。
二.求出能推出空的非终结符
1.字典 x 标记所有非终结符的初始状态为未定,用 0 表示(能推出空 & 则为 1 ,不能则为 2 )。
2.删除右部有终结符的产生式。若某非终结符对应的产生式都被删除,则将该非终结符标记为 2 ,即不能推出空 & 。
3.若有产生式右部为空 & ,则将左部非终结符标记为 1 ,然后删除该非终结符为左部的所有产生式。
4.扫描每个产生式右部符号,若为能推出空的非终结符则删除,若该符号删除后右部为空,则将左部非终结符标记为 1,并删除该非终结符为左部的所有产生式。
5.扫描每个产生式右部符号,若有不能推出空的非终结符,则删去该产生式,若则使该左部非终结符的所有产生式全被删除,则将该非终结符标记为 2 。
6.重复 4、5 直到字典 x 不再变化,最后得到的字典 x 即记录了非终结符能否推出空。
tips:重复计算直到不再变化,可能好理解,但不好实现,可以设置一个布尔型标志标量,作为循环条件,初始为True,每次进入循环就置为False,如果目标集合发生改变,就置为True。如果用集合做运算可能不好知道目标集合是否改变,可以用一个临时变量提前存储目标集合,计算后再比较是否变化,这里要注意python赋值的浅拷贝问题,记得要深拷贝,可以看我前一篇博文。
tips:更新。突然想到更新目标集合的都是并集运算,集合元素只增不减,可以用临时变量保存计算前长度就行,后面直接比较长度就知道目标集合是否变化。深拷贝再比较肯定会更耗时。
三.计算 first 集合
1.若 A 为终结符,则 first(A) = {A}
2.若 A 为非终结符且能推出空,则将空 & 加入 first(A)
3.遍历产生式,若右部第一个符号为终结符,形如 A->a…,A为非终结符,a为终结符,则将 a 加入 first(A)
4.对每个产生式,从右部第 Xi个符号(i 从 1 开始)开始遍历,若 Xi 能推出空,则将 first(Xi) - {&} 加入 first(左部非终结符),若Xi不能推出空 则将first(Xi) 加入 first(左部非终结符),然后遍历下一个产生式右部,i又从1开始。
5.重复 4 直到 每个符号的 first 集合都不再变化。
6.根据 5 得到每个符号的 first 集合,求每个产生式右部字符串的 first 集合,原理类似 4 ,若右部第一个字符能推出空,就将 first()-{&} 加入字符串的 first() ,然后看第二个…若第n个字符不能推出空,则将 first() 加入字符串的 first() ,然后不再往下看。若一直到倒数第二个字符都能推出空,则处理最后一个字符时,直接将 first() 加入 字符串的 first()中。
tips:写代码时可以循环处理,先将当前字符 first()-{&} 加入正在求解的first() 中,然后再看 & 在不在当前字符 first() 中,不在就break跳出循环。
tips:这里所说的加入操作都是指并集操作 ,将B加入A 即 A = A U B
tips:刚开始写博客,很多数学符号不会敲,见怪不怪啦,看不懂的地方烦请评论区留言或者私我。
四.计算 follow 集合