編譯原理中必不可少的算法:中綴表達式轉後綴表達式

點贊再看,養成習慣!覺得不過癮的童鞋,歡迎關注公衆號《機器學習算法工程師》,有非常多大神的乾貨文章可供學習噢…

前言

這篇文章是對參考文獻中NFA文章的補充,根據正規式構造NFA的步驟中有中綴表達式轉後綴表達式的步驟,小編在這篇文章中將以耳熟能詳的算式表達式(如:10+3-2*(5/1))做例子,講解中綴表達式轉後綴表達式的原理。基本邏輯是:構造符號優先關係表、計算優先函數、中綴表達式轉後綴表達式的流程。

正文

爲簡化模型,小編將字母d作爲數字的抽象符號,還有用於指示表達式開始和結束的符號#,那麼只包含簡單的四則運算規則的算式表達式將包含的符號集爲:S={d,+,,,/,(,),#}S = \{d,+,-,*,/,(,),\#\}

構造符號優先關係表

構造符號優先關係表是屬於語義分析的知識點,按理說是要先去了解算符優先分析法,不過在這裏,小編更希望給童鞋們一個直觀上的認識,就跳過概念直接給出了下面的優先關係表。小編從工程實踐的角度來幫助大家認識這個優先關係表,首先我們需要知道isp和icp都是什麼,前者指的是在棧頂的符號優先級,後者指的是從輸入的中綴表達式中讀取到的當前符號優先級。然後,我們需要知道在表格中那些{>,<,空}都意味着什麼,{>,<}就是我們瞭解到的大於小於,而空則表示這種情況在中綴表達式構造正確的情況下不會出現isp的那個棧頂元素與icp當前元素相遇的情況,可以舉個例子,如果中綴表達式爲d+d(d+d)這種情況,d(d就是不正確的構造,中間缺少運算符。最後,我們需要知道這些關係是如何得來的,這其實就是人爲設定的規則,具體有:

  • 先括號內,再括號外;
  • 乘法除法優先級高於加法減法;
  • 同優先級情況下,自左向右;
  • 開始結束符號#優先級最低,數字d優先級最高,互相匹配的符號優先級相同。
isp\icp + - * / ( ) # d
+ > > < < < > > <
- > > < < < > > <
* > > > > < > > <
/ > > > > < > > <
( < < < < < = <
) > > > > > >
# < < < < < = <
d > > > > > >

對這個優先關係表構造有興趣的童鞋,小編建議去看看參考文獻中那本書籍,這個知識點以及下面計算優先函數的知識點都在第6章自底向上優先分析中。

計算優先函數

其實有了上面一節的優先關係表,我們就可以直接開始中綴表達式轉後綴表達式的流程,怎麼做呢?最簡單的就是使用一個哈希結構,key便是一個元組(c1,c2),c1表示當前棧頂符號,c2表示當前讀取的符號,取出value,便是優先級(0代表相同,1表示c1優先級大於c2,-1表示c1優先級小於c2,-2表示空,程序應該中止報錯)。但爲什麼還要有這一計算優先函數的一步呢?優先函數是什麼呢?這完全是從節省存儲空間的角度出發想出的替代方法,原先優先關係表會佔用nxn個內存單元,n表示符號集元素個數,但是如果能夠轉化爲優先函數(即isp和icp函數,輸入爲符號ch,輸出爲該符號ch的優先級數值),那麼只需2n個單元,將大大節省存儲空間。
那麼優先關係表如何得到優先函數呢?這其實也有嚴格的理論,不過小編這裏還是直接給出最終的結果,鼓勵童鞋們自行探索。

+ - * / ( ) # d
isp 3 3 5 5 1 5 1 5
icp 2 2 4 4 6 1 1 6

下面是小編編寫的轉化代碼,供童鞋們參考:

"""
@description:
根據優先關係表計算優先函數
@author: 一帆
@date: 2020/6/10
"""

def getPriorityFunc(pt,n):
    # 初始化isp/icp
    pfunc = [[1]*n for _ in range(2)]
    # 迭代,直至flag不再變動或者超過限制的迭代輪數
    flag = False
    iter = 1
    limit_iter = 10
    while not flag and iter<=limit_iter:
        print("迭代輪數:",iter)
        iter+=1
        for a in range(n):
            for b in range(n):
                if pt[a][b]==1 and pfunc[0][a]<=pfunc[1][b]:
                    #isp(a)優先級高於icp(b)且isp(a)<=icp(b),則isp(a)=icp(b)+1
                    pfunc[0][a]=pfunc[1][b]+1
                    flag=True
                elif pt[a][b]==-1 and pfunc[0][a]>=pfunc[1][b]:
                    pfunc[1][b]=pfunc[0][a]+1
                    flag=True
                elif pt[a][b]==0 and pfunc[0][a]!=pfunc[1][b]:
                    if pfunc[0][a]<pfunc[1][b]:
                        pfunc[0][a]=pfunc[1][b]
                    else:
                        pfunc[1][b]=pfunc[0][a]
                    flag=True
        if not flag:
            return pfunc
        else:
            flag=False
    return None


if __name__ == '__main__':
    pt = [[1,1,-1,-1,-1,1,1,-1],
          [1,1,-1,-1,-1,1,1,-1],
          [1,1,1,1,-1,1,1,-1],
          [1,1,1,1,-1,1,1,-1],
          [-1,-1,-1,-1,-1,0,-2,-1],
          [1,1,1,1,-2,1,1,-2],
          [-1,-1,-1,-1,-1,-2,0,-1],
          [1,1,1,1,-2,1,1,-2]]
    pfunc = getPriorityFunc(pt,len(pt))
    print(pfunc)

中綴表達式轉後綴表達式

經過了前面的準備工作,有了isp和icp這兩個優先函數,我們終於可以着手開始中綴表達式向後綴表達式的轉化了。基本邏輯如下圖所示:
在這裏插入圖片描述
對應的代碼如下:

"""
@description:
實現中綴表達式向後綴表達式地轉化
符號集:數字[d]、四則運算[+-*/]和括號[()]
@author: 一帆
@date: 2020/6/10
"""

import sys


class In2Postfix:
    def __init__(self, infix_expression):
        self.__infix = infix_expression
        self.__postfix = ""
        self.__isp = {'+': 3, '-': 3, '*': 5, '/': 5, '(': 1, ')': 5, '#': 1, 'd': 5}
        self.__icp = {'+': 2, '-': 2, '*': 4, '/': 4, '(': 6, ')': 1, '#': 1, 'd': 6}

    def ispFunc(self, char):
        priority = self.__isp.get(char, -1)
        if priority == -1:
            print("error: 出現未知符號!")
            sys.exit(1)  # 異常退出
        return priority

    def icpFunc(self, char):
        priority = self.__icp.get(char, -1)
        if priority == -1:
            print("error: 出現未知符號!")
            sys.exit(1)  # 異常退出
        return priority

    def in2post(self):
        infix = self.__infix + '#'
        stack = ['#']
        loc = 0
        while stack and loc < len(infix):
            c1, c2 = stack[-1], infix[loc]
            if self.ispFunc(c1) < self.icpFunc(c2):
                # 棧外字符優先級更高,壓棧
                stack.append(c2)
                loc += 1  # 前進
            elif self.ispFunc(c1) > self.icpFunc(c2):
                # 棧頂字符優先級更高,彈出
                self.__postfix += stack.pop()
            else:
                # 優先級相等,要麼結束了,要麼碰到右括號了,都彈出但並不添加至後綴表達式中
                stack.pop()
                loc += 1

    def getResult(self):
        self.in2post()
        return self.__postfix


if __name__ == '__main__':
    infix_expression = "d+d-d*(d/d)"  # 簡單測試
    # infix_expression = input("請輸入算式表達式:")
    print("infix_expression:", infix_expression)
    solution = In2Postfix(infix_expression)
    print("postfix_expression:", solution.getResult())

結語

其實小編還是有點疑惑的,根據正規式構造NFA是詞法分析的知識點,但是其構造過程卻涉及到本篇文章講述的算法,而這個算法是在後面語義分析纔會學到的,所以小編想這應該是爲什麼有那麼多童鞋找小編要NFA的源碼的原因吧。本篇中的重難點知識點集中在符號優先級關係的確定與優先函數的計算,理論性非常強,但編程實現卻並不難,建議正在學習編譯原理的童鞋儘量自行實現,實在卡住了再來參考借鑑。

參考文獻

  1. 張素琴. 《編譯原理》第二版
  2. NFA: https://blog.csdn.net/gongsai20141004277/article/details/52949995

童鞋們,讓小編聽見你們的聲音,點贊評論,一起加油。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章