本文实现了一个较为简单的算法——文法的左公因子提取。umm其实也称不上是算法。
我们的目标非常明确,就是将文法产生式的左公因式都提出来,需要注意以下几点:
-
在一个产生式中需要提取范围最大的公因式,即拥有该左公因式的右部应该尽可能得多
-
提取左公因子之后会产生新的产生式,该产生式也可能存在左公因子,需要一并提取,因此是一种递归的思想
-
好像也就这两点…
那么我们只需要从广度和深度进行确定即可。首先确定左公因子广度,即最多的含有左公因子的右部个数。很显然,对于最大的广度,只要找第一个符号相等的右部即可。确定最大广度后,我们再求这些首字符相等右部的最大深度(最长的左公因子)即可,那么就一遍一遍地遍历,直到发现其中一个右部和其他右部的第位字符不同时,退出循环。构造新产生式,然后对新产生式进行左公因子提取即可。
C ☺ D E
def extract(gen, left):
gen[left].sort()
k = 0
flag = True
next_str = left + "'"
while k < len(gen[left]) - 1:
kk = k + 1
# 确定横向最大距离
while kk < len(gen[left]) and gen[left][kk][0] == gen[left][kk - 1][0]:
kk += 1
if kk == k + 1:
k += 1
continue
# 确定纵向最大深度
common = gen[left][k][0]
depth = 0
for depth in range(1, len(gen[left][k])):
flag = True
for i in range(k + 1, kk):
if depth >= len(gen[left][i]) or gen[left][i][depth] != gen[left][i - 1][depth]:
flag = False
break
if flag:
common += gen[left][k][depth]
else:
break
# 当整个gen[left][k]均为左公因式(flag始终为True)时必须将depth+=1,因为depth指的是左公因式的后一位
depth += (1 if flag else 0)
# extract common part
gen[next_str] = ([gen[left][k][depth:]] if depth < len(gen[left][k]) else ['ε'])
gen[left][k] = common + next_str
for i in range(k + 1, kk):
gen[next_str].append((gen[left][k + 1][depth:] if depth < len(gen[left][k + 1]) else ['ε']))
gen[left].pop(k + 1)
# 递归调用
extract(gen, next_str)
next_str += "'"
k = k + 1
n = eval(input('请输入文法产生式的个数:'))
print('请输入文法产生式:')
gen = dict()
for i in range(n):
g = input().replace(' ', '')
assert g[1:3] == '->'
gen.setdefault(g[0], [])
start = 3
for i in range(3, len(g)):
if g[i] == '|' or i == len(g) - 1:
i += (1 if i == len(g) - 1 else 0)
assert start < i
gen[g[0]].append(g[start:i])
start = i + 1
extract(gen, g[0])
print("\nAfter left factoring:")
for key in gen:
print(key + ' -> ', end='')
print(*gen[key], sep=' | ')
Tips
-
数据结构与<编译原理> 左递归消除算法的类似,只是列表中无需分终结符开头还是非终结符开头,提取左公因子一视同仁
-
由于相同字符开头的右部可能很分散,不太好直接找,因此我们在提取之前可以对一个产生式的右部先按照字符序进行排序
-
用
[k,kk)
来表示最大广度范围,depth
是最大深度,next_str
是下一个新产生式左部的名称(就是每次加一个【‘】) -
注意有时其中一个右部本身就是左公因式的特殊情况,此时提取后的新产生式右部需要加上【ε】