php數學公式解析 看看架構師是如何架構設計的


最近工作中需要用到公式解析工具,我就在網上搜索到了一段代碼,

public class Expression
{
private StackSimple<int> theStack;
public StringBuilder suffix = new StringBuilder();
/**
* 檢查該字符是否為括號
*/
private bool isParen(char c)
{
if (c == '{' || c == '[' || c == '(' || c == ')' || c == ']' || c == '}')
return true;
else
return false;
}
/**
* 檢查是否為左括號
*/
private bool isLeftParen(char c)
{
if (c == '{' || c == '[' || c == '(')
return true;
else
return false;
}
/**
* 判斷指定的操作符是否為 '+' 或者 '-'
*/
private bool isAddOrSub(int oper)
{
return oper == '+' || oper == '-';
}
/**
* 檢查是否為數字
*/
private bool isNumber(int ch)
{
return ch >= 48 && ch <= 57;
}
/**
* 檢查表達式是否合法
*/
private bool checkExp(String exp)
{
char[] ch = exp.ToCharArray();
StackSimple<int> theStackXInt = new StackSimple<int>(exp.Length / 2);
/**
* 遍歷表達式,只處理括號部分
*/
for (int i = 0; i < ch.Length; i++)
{
char c = ch[i];
if (isParen(c))
{
/**
* 左括號進棧
*/
if (isLeftParen(c))
{
theStackXInt.Push(c);
}
else
{
if (theStackXInt.IsEmpty())
{
return false;
}
int left = theStackXInt.Pop();
switch (c)
{
case '}':
if (left != '{')
return false;
break;
case ']':
if (left != '[')
return false;
break;
case ')':
if (left != '(')
return false;
break;
}
}
}
}
if (theStackXInt.IsEmpty())
{
return true;
}
else
{
return false;
}
}
/**
* 根據當前操作符處理之前在棧中的操作符,當前操作符暫不處理,放入棧中
*/
private void processOper(char oper)
{
while (!theStack.IsEmpty())
{
int currTop = theStack.Pop();
/**
* 如果是左括號,則不予處理,將括號返回進棧
*
* 若得到的操作符,則進一步判斷
*/
if (currTop == '(')
{
theStack.Push(currTop);
break;
}
else
{
if (this.isAddOrSub(currTop))
{
if (this.isAddOrSub(oper))
{
this.suffix.Append((char)currTop);
}
else
{
this.theStack.Push(currTop);
break;
}
}
else
{
this.suffix.Append((char)currTop);
break;
}
}
}
/**
* 當前操作符進棧
*/
theStack.Push(oper);
}
/**
* 處理括弧.
*
* 如果為左括弧,直接進棧;
*
* 否則為右括弧,現將這一對括號中的操作符優先處理完
*/
private void processParen(char paren)
{
if (this.isLeftParen(paren))
{
this.theStack.Push(paren);
}
else
{
while (!theStack.IsEmpty())
{
int chx = theStack.Pop();
if (chx == '(')
break;
else
suffix.Append((char)chx);
}
}
}
/**
* 將正確的中綴表達式轉為后綴表達式
*/
private void doTrans(String exp)
{
theStack = new StackSimple<int>(exp.Length);
if (this.checkExp(exp))
{
char[] ch = exp.ToCharArray();
for (int i = 0; i < ch.Length; i++)
{
char c = ch[i];
switch (c)
{
case '+':
case '-':
case '*':
case '/':
this.processOper(c);
break;
case '(':
case ')':
this.processParen(c);
break;
default:
this.suffix.Append(c);
break;
}
}
while (!this.theStack.IsEmpty())
{
this.suffix.Append((char)this.theStack.Pop());
}
}
}
/**
* 計算表達式
*/
public int clac(String exp)
{
this.doTrans(exp);
String suff = this.suffix.ToString();
StackSimple<int> stack = new StackSimple<int>(suff.Length);
if (suff == null || suff.Length == 0)
{
}
else
{
char[] ch = suff.ToCharArray();
for (int i = 0; i < ch.Length; i++)
{
char c = ch[i];
if (this.isNumber(c))
{
stack.Push(c - 48);
}
else
{
int operand2 = stack.Pop();
int operand1 = stack.Pop();
switch (c)
{
case '+':
stack.Push(operand1 + operand2);
break;
case '-':
stack.Push(operand1 - operand2);
break;
case '*':
stack.Push(operand1 * operand2);
break;
case '/':
stack.Push(operand1 / operand2);
break;
}
}
}
}
return stack.Peep();
}
}

先解釋下這個類的執行過程:遍歷一個字符串公式,如果是數字,放到棧里,非則放到臨時棧里,如果發現+或者-,就把臨時棧里的top取出放入棧里,如果發現的是非+和-(因為+和-優先級太低了。。。)放進臨時棧里,如果發現的是括號,則把臨時棧top進棧里,直到發現左括號為止。。。。。。。。。。。。

因為我用的是php,所以我把這段代碼翻譯成php的,這段代碼看上去就像是一坨屎啊,下面我要把這坨屎重構成香餑餑........

第一步:首先要弄明白公式解析是啥東西,怎么個流程,包含哪些步驟,這是一個工具,用來解析公式字符串的,沒錯,解析公式字符串,那么公式字符串呢,我們要先判斷公式是否合法,然后呢,既然合法了,那就解析啊,解析成程序可以識別的數據(棧),再燃后呢,既然都能識別了,那就是計算啊.....

第二部步:現在開發思考,怎么設計類結構了,根據上一步的分析,大體上有3個類,Expression(主類)、CheckExpression(檢查是否合法,並返回可識別棧)、operateclass(計算類),這個代碼就出來了。

現在針對上面的類進行重構:

1、先看主類,因為這是個工具,一個急救箱能救無數人,而不是每一個人配一個急救箱,是唯一對象,所以要設計成單類模式。

2、CheckExpression和operatecass這兩個類是為主類服務的,脫離了主類就沒有意義了,所以這兩個類和主類是聚合關系。

3、php的函數映射就是屌,擴展起來很方便,直接上代碼:

class OperateClass
{
public $arr = array(
"+" => "add",
"-" => "sub",
);
public function OpreateArr($a, $b)
{
$func = new $operate();
return $func($a,$b);
}
private function add($a,$b)
{
return $a + $b;
}
private function sub($a,$b)
{
return $a + $b;
}
}
有興趣的同學可以把參數改成數組,面對多個參數的數學公式時,容易擴展。

4、個人的一個習慣,寫類的時候總喜歡先寫個cleanup()方法。。。強迫症

5、好多地方都要加上異常判斷,比如除數是0等等。

6、checkExp(String exp)和doTrans(exp) 這兩個函數里都有for循環,這兩個函數可以整合成一個函數,提高性能。

大體上就先寫到這里吧,架構師干的不就是架構框架、重構代碼、優化提高性能嘛,咱們這個公式解析類,麻雀雖小,五臟俱全。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM