PHP 實現後綴表達式(接受四則運算字符串,輸出計算結果,附代碼)

最近接觸了一個有趣的需求:給定變量a、b、c、d等若干,要求由用戶輸入的普通四則運算字符串(包含加減乘除括號),算出具體的值。

例如,a=1,b=2,c=3,d=4,給出 a+b/(d-c),應計算出結果爲3,若爲 a*b/(c-1) 則應計算出結果爲1

這種情況下,第一反應可能是用數字值將字符串裏的變量替換,然後通過eval()執行。或者是將字符串中的每一項通過正則一個一個扣出來再進行計算。

但這樣的邏輯太粗暴,代碼也太醜陋,其實大可不必如此。 此時,讓我們將目光移向美麗的數據結構與算法。

首先,我們瞭解一下 後綴表達式。

表達式一般由操作數(Operand)、運算符(Operator)組成,例如算術表達式中,通常把運算符放在兩個操作數的中間,
這稱爲中綴表達式(Infix Expression),如A+B。我們日常生活中使用的就是此種方式
波蘭數學家Jan Lukasiewicz提出了另一種數學表示法,它有兩種表示形式:
把運算符寫在操作數之前,稱爲波蘭表達式(Polish Expression)或前綴表達式(Prefix Expression),如+AB;
把運算符寫在操作數之後,稱爲逆波蘭表達式(Reverse Polish Expression)或後綴表達式(Suffix Expression),如AB+

我們今天要探討的就是後綴表達式

我們如何使用它呢?

首先我們要將平時用的中綴表達式轉爲後綴表達式。

在此之前,我們需要一種常見的數據結構:,在這一步,我們需要兩個棧,操作數棧運算符棧

1、從左至右掃描一中綴表達式。

2、若讀取的是操作數,則判斷該操作數的類型,並將該操作數存入操作數棧

3、若讀取的是運算符

(1) 該運算符爲左括號”(“,則直接存入運算符棧。

(2) 該運算符爲右括號”)”,則輸出運算符棧中的運算符到操作數棧,直到遇到左括號爲止。

(3) 該運算符爲非括號運算符:

 (a) 若比運算符棧棧頂的運算符優先級高或相等,則直接存入運算符棧。
 (b) 若比運算符棧棧頂的運算符優先級低,則輸出棧頂運算符到操作數棧,並將當前運算符壓入運算符棧。

4、當表達式讀取完成後運算符棧中尚有運算符時,則依序取出運算符到操作數棧,直到運算符棧爲空。

此時,將操作數棧轉爲一個字符串,它就是由我們輸入的中綴表達式轉化而來的後綴表達式

那麼後綴表達式如何進行計算呢?

1、從左到右掃描後綴表達式。

2、如果掃描的項目是操作數,則將其壓入操作數棧,並掃描下一個項目。

3、如果掃描的項目是一個二元運算符,則對棧的頂上兩個操作數執行該運算。

4、如果掃描的項目是一個一元運算符,則對棧的最頂上操作數執行該運算。

5、將運算結果重新壓入棧。

6、重複步驟2-5,直到後綴表達式掃描完畢,棧中即爲結果值。


Talk is cheap,let me show you the code

接下來是使用PHP編寫的這樣一個工具類。可以接受傳入一個表達式和表達式各項對應值 的數組,給出計算之後的結果。

使用方式:

RPNotation::calculate($exp, $exp_values);

例如:

$exp = "a+b-(c*d)/e";
$exp_values = ["a" => 1, "b" => 2, "c" => 3, "d" => 2, "e" => 3];
RPNotation::calculate($exp, $exp_values);

結果會是1


代碼內容 :

<?php

//將用戶輸入的表達式轉爲逆波蘭表達式計算

class RPNotation { 

    //正則表達式,用於將表達式字符串,解析爲單獨的運算符和操作項
    const PATTERN_EXP = '/((?:[a-zA-Z0-9_]+)|(?:[\(\)\+\-\*\/])){1}/';
    const EXP_PRIORITIES = ['+' => 1, '-' => 1, '*' => 2, '/' => 2, "(" => 0, ")" => 0];

    /*
    params:
          $exp-普通表達式,例如 a+b*(c+d)
          $exp_values-表達式對應數據內容,例如 ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]
    */
    public static function calculate($exp, $exp_values) {
        $exp_arr = self::parse_exp($exp);//將表達式字符串解析爲列表
        if (!is_array($exp_arr)) {
            return NULL;
        }
        $output_queue = self::nifix2rpn($exp_arr);
        return self::calculate_value($output_queue, $exp_values);
    }

    //將字符串中每個操作項和預算符都解析出來
    protected static function parse_exp($exp) {
        $match = [];
        preg_match_all(self::PATTERN_EXP, $exp, $match);
        if ($match) {
            return $match[0];
        }else {
            return NULL;
        }
    }

    //將中綴表達式轉爲後綴表達式
    protected static function nifix2rpn($input_queue){
        $exp_stack = [];
        $output_queue = [];
        foreach($input_queue as $input) {
            if (in_array($input, array_keys(self::EXP_PRIORITIES))){
                if ($input == "(") {
                    array_push($exp_stack, $input);
                    continue;
                }
                if ($input == ")") {
                    $tmp_exp = array_pop($exp_stack);
                    while ($tmp_exp && $tmp_exp != "(") {
                        array_push($output_queue, $tmp_exp);
                        $tmp_exp = array_pop($exp_stack);
                    }
                    continue;
                }
                foreach(array_reverse($exp_stack) as $exp) {
                    if (self::EXP_PRIORITIES[$input] <= self::EXP_PRIORITIES[$exp]) {
                        array_pop($exp_stack);
                        array_push($output_queue, $exp);
                    }else {
                        break;
                    }
                }
                array_push($exp_stack ,$input);
            }else {
                array_push($output_queue, $input);
            }
        }
        foreach(array_reverse($exp_stack) as $exp) {
            array_push($output_queue, $exp);
        }
        return $output_queue;
    }

    //傳入後綴表達式隊列、各項對應值的數組,計算出結果
    protected static function calculate_value($output_queue, $exp_values) {
        $res_stack = [];
        foreach($output_queue as $out) {
            if (in_array($out, array_keys(self::EXP_PRIORITIES))) {
                $a = array_pop($res_stack);
                $b = array_pop($res_stack);
                switch ($out) {
                    case '+':
                        $res = $b + $a;
                        break;
                    case '-':
                        $res = $b - $a;
                        break;
                    case '*':
                        $res = $b * $a;
                        break;
                    case '/':
                        $res = $b / $a;
                        break;
                    }
                array_push($res_stack, $res);
            }else {
                if (is_numeric($out)) {
                    array_push($res_stack, intval($out));
                }else {
                    array_push($res_stack, $exp_values[$out]);
                }
            }
        }
        return count($res_stack) == 1 ? $res_stack[0] : NULL;
    }

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