天天看點

C++版 逆波蘭式 解析四則運算字元串

#include <stack>
#include <string>
#include <memory>
using namespace std;

/*本工具提供四則運算解析功能

	使用時,引入本檔案即可。
	通過調用 Chion::Math::ParsStr("100+200*3/(54+89)");即可得到結果
	注意:如果輸入的字元串非法,則傳回結果是一個空指針。

	如果你對算法有興趣,那麼下面的說明可以了解一下:
	我對輸入的原始的字元串稱為源字元串SourceString
	對于從SourceString解析下來的一個元素稱為SourceElement
		SourceElement有可能是一個數字,一個運算符,一個左括号,一個右括号或者為空。
		兩個SourceElement如果都是運算符,可以進行優先級比較:
			一種方法是調用SourceElement1.Compare(SourceElement2)
			另一種方法是用減法運算 SourceElement1 -SourceElement2
			根據結果來判斷其大小:
				1:SourceElement1大于SourceElement2 
				0:SourceElement1等于SourceElement2 
				-1:SourceElement1小于SourceElement2
				-2:錯誤,說明兩個中存在不是運算符的情況
	通過SourceString::GetNextObject()可以不斷解析到新的元素。直到SourceElement為空,表示結束。

	算法如下(算法思路摘自百度百科):
	首先需要配置設定2個棧,一個作為臨時存儲運算符的棧S1,一個作為輸入逆波蘭式的棧S2(空棧),從中綴式的左端開始取字元,逐序進行如下步驟:
	(1)若取出的字元是操作數,則分析出完整的運算數,該操作數直接送入S2棧
	(2)若取出的字元是運算符,則将該運算符與S1棧棧頂元素比較,如果該運算符優先級(不包括括号運算符)大于S1棧棧頂運算符優先級,則将該運算符進S1棧,否則,将S1棧的棧頂運算符彈出,送入S2棧中,直至S1棧棧頂運算符低于(不包括等于)該運算符優先級,最後将該運算符送入S1棧。
	(3)若取出的字元是“(”,則直接送入S1棧頂。
	(4)若取出的字元是“)”,則将距離S1棧棧頂最近的“(”之間的運算符,逐個出棧,依次送入S2棧,此時抛棄“(”。
	(5)重複上面的1~4步,直至處理完所有的輸入字元
	(6)當所有字元處理完之後,如果S1中還有資料,則逐個出棧,依次送入S2棧。
	完成以上步驟,S2棧便為逆波蘭式輸出結果。不過S2應做一下逆序處理。便可以按照逆波蘭式的計算方法計算了!
*/

namespace Chion{
	/*枚舉了元素對象可能存在的情況*/
	typedef enum{
		TYPE_EMPTY,			//空
		TYPE_NUMBER,		//數字
		TYPE_OPERATOR,		//運算符
		TYPE_BRACKETS_L,	//左括号
		TYPE_BRACKETS_R,	//右括号
	}ENUM_OBJECT_TYPE;

	/*元素對象,它表示從源字元串上解析出來的一個單元*/
	struct SourceElement
	{
		/*構造一個空的對象*/
		SourceElement(){
			m_type = TYPE_EMPTY;
			m_value = 0.0;
			m_op = '\0';
		}

		/*構造一個括号對象,如果參數有誤,則構造一個空對象*/
		SourceElement(ENUM_OBJECT_TYPE type){
			m_value = 0.0;
			m_op = '\0';
			switch (type)
			{
			case TYPE_BRACKETS_L:
			case TYPE_BRACKETS_R:
				m_type = type;
				break;
			case TYPE_EMPTY:
			case TYPE_NUMBER:
			case TYPE_OPERATOR:
			default:
				m_type = TYPE_EMPTY;
				break;
			}
		}

		/*構造一個操作符對象,如果參數有誤,則構造一個空對象。參數取值隻能是'+'、'-'、'*'、'/'*/
		SourceElement(char op){
			if (op == '+'
				||
				op == '-'
				||
				op == '*'
				||
				op == '/'
				)
			{
				m_type = TYPE_OPERATOR;
				m_op = op;
			}
			else
			{
				m_type = TYPE_EMPTY;
				m_op = '\0';
			}
			m_value = 0.0;
		}

		/*構造一個數字對象*/
		SourceElement(double value)
		{
			m_type = TYPE_NUMBER;
			m_value = value;
			m_op = '\0';
		}

		ENUM_OBJECT_TYPE type() const{
			return m_type;
		}

		/*當類型為數字時,傳回數值。其它類型時,傳回0,此時0無意義。*/
		double value(){
			if (m_type == TYPE_NUMBER)
			{
				return m_value;
			}
			else
			{
				return 0.0;
			}
		}

		/*當類型為操作符時,傳回操作符'+' '-' '*' '/'。其它類型時,傳回'\0'*/
		char op(){
			if (m_type == TYPE_OPERATOR)
			{
				return m_op;
			}
			else
			{
				return '\0';
			}

		}

		int Compare(const SourceElement& obj)
		{
			if (this->m_type != ENUM_OBJECT_TYPE::TYPE_OPERATOR
				||
				obj.type() != ENUM_OBJECT_TYPE::TYPE_OPERATOR)
			{
				return -2;
			}
			else
			{
				if (this->m_op == '*' || this->m_op == '/')
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return 0;
					}
					else
					{
						return 1;
					}
				}
				else
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return -1;
					}
					else
					{
						return 0;
					}
				}

			}

		}

		int operator-(const SourceElement &obj){
			if (this->m_type != ENUM_OBJECT_TYPE::TYPE_OPERATOR
				||
				obj.type() != ENUM_OBJECT_TYPE::TYPE_OPERATOR)
			{
				return -2;
			}
			else
			{
				if (this->m_op == '*' || this->m_op == '/')
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return 0;
					}
					else
					{
						return 1;
					}
				}
				else
				{
					if (obj.m_op == '*' || obj.m_op == '/')
					{
						return -1;
					}
					else
					{
						return 1;
					}
				}

			}
		}
	private:
		ENUM_OBJECT_TYPE m_type;
		double m_value;
		char m_op;
	};

	/*源字元串的封裝對象,封裝對源字元串的一些操作*/
	struct SourceString{
		SourceString(const string &str)
			:m_pos(0){
			//隻保留四則運算中的有用字元
			for (auto &e : str)
			{
				if (
					(e >= '0' && e <= '9')
					||
					e == '.'
					||
					e == '+' || e == '-' || e == '*' || e == '/'
					||
					e == '(' || e == ')'
					)
				{
					m_str += e;
				}
			}
		}

		/*
			從m_pos位置開始,在源字元串上解析出一個元素,解析完畢,m_pos指向元素下一個位置
			如果解析失敗,m_pos已經超出字元串範圍,傳回一個type為空的對象。
		*/
		SourceElement GetNextObject()
		{
			//解析到了盡頭,無法再繼續解析,傳回空對象
			if (m_pos >= m_str.size())
			{
				return SourceElement();
			}

			//擷取一個字元
			char c = m_str[m_pos];
			switch (c)
			{
				//運算符号,合法(遊标可進位)
			case '+':
			case '*':
			case '/':
				m_pos++;
				return SourceElement(c);
			/*
				如果遇到減号,需要獨立判斷,看這是一個運算符,還是屬于數字前面的負号
				當它滿足兩個條件時,該減号為負号:
					1.它的後面是數字
					2.它的前面是加減乘除或者左括号或者前面沒有其它字元
			*/
			case '-':
				if (m_pos + 1 < m_str.size() && m_str[m_pos + 1] >= '0' && m_str[m_pos + 1] <= '9'
					&&
					(m_pos == 0||
					m_str[m_pos - 1] == '('||
					m_str[m_pos - 1] == '+'||
					m_str[m_pos - 1] == '-'||
					m_str[m_pos - 1] == '*'||
					m_str[m_pos - 1] == '/'))
				{
					string number = ParseNumber();
					return number.size() ? SourceElement(atof(number.c_str())) : SourceElement();
				}
				else
				{
					m_pos++;
					return SourceElement(c);
				}

			//左括号,合法(遊标可進位)
			case '(':
				m_pos++;
				return SourceElement(TYPE_BRACKETS_L);
				//右括号,合法(遊标可進位)
			case ')':
				m_pos++;
				return SourceElement(TYPE_BRACKETS_R);
			case '.'://小數點出現在第一個字元是不正常的,非法(遊标不進位)
				return SourceElement();
			default://數字,需要連續解析
			{
				string number = ParseNumber();		//要解析的數字
				return number.size()? SourceElement(atof(number.c_str())):SourceElement();
			}
			}
		}
	private:
		/*
			它從m_pos位置開始,解析出一個數字來。解析完畢後,m_pos指向數字的下一個位置。
			如果解析失敗,它傳回空字元串
		*/
		string ParseNumber(){
			string number;
			bool bDot(false);	//小數點是否出現的标志
			//往後繼續解析
			for (; m_pos < m_str.size();)
			{
				//擷取一個字元
				char n = m_str[m_pos];
				if (n == '-' && number.size()==0)//如果是減号,且出現在開頭位置,表示這是一個負号
				{
					number += n;
					m_pos++;
				}
				else if (n >= '0' && n <= '9')	//如果是數字,則追加到number,遊标進位
				{
					number += n;
					m_pos++;
				}
				else if (n == '.')		//如果是小數點,首次出現是合法的,追加并遊标進位。否則判為非法,遊标不進位
				{
					if (bDot == false)
					{
						number += n;
						m_pos++;
						bDot = true;
					}
					else
					{
						return "";
					}
				}
				else//如果是其它字元,表示在此終止,遊标不進位,停留在該字元上
				{
					break;
				}
			}

			return number;
		}
	private:
		string m_str;
		int m_pos;
	};

	class Math{
	public:
		static std::shared_ptr<double> ParseStr(const string &sourceStr){
			SourceString source(sourceStr);
			stack<SourceElement> s1;		//運算符棧
			stack<SourceElement> s2;		//逆波蘭式棧
			//從左至右逐個對象地解析
			for (SourceElement obj = source.GetNextObject();
				obj.type() != ENUM_OBJECT_TYPE::TYPE_EMPTY;
				obj = source.GetNextObject())
			{
				switch (obj.type())
				{
				case ENUM_OBJECT_TYPE::TYPE_NUMBER:	//數字直接入棧
					s2.push(obj);
					break;
				case ENUM_OBJECT_TYPE::TYPE_OPERATOR://運算符則要判斷來處理
				{
					//如果棧頂元素是運算符,且優先級低于等于目前運算符,則彈出放入s2。
					while(
							s1.size()
							&&
							(
								obj.Compare(s1.top()) == 0	//等于
								||
								obj.Compare(s1.top()) == -1	//低于
							)
						)
					{
						s2.push(s1.top());
						s1.pop();
					}
					s1.push(obj);
				}
					break;
				case ENUM_OBJECT_TYPE::TYPE_BRACKETS_L:	//左括号直接入棧
					s1.push(obj);
					break;
				case ENUM_OBJECT_TYPE::TYPE_BRACKETS_R:	//右括号,清空S1棧頂,直到遇見左括号或清空全部
				{
					while (s1.size())
					{
						if (s1.top().type() == TYPE_OPERATOR){
							s2.push(s1.top());
							s1.pop();
						}
						else if (s1.top().type() == TYPE_BRACKETS_L)
						{
							s1.pop();
							break;
						}
						else
						{
							break;
						}
					}
				}
					break;
				default:
					break;
				}
			}

			//把S1剩下的都放入S2
			while (s1.size())
			{
				s2.push(s1.top());
				s1.pop();
			}

			//由于s2中的逆波蘭式是倒過來的,我們先把它倒置到s1中
			while (s2.size())
			{
				s1.push(s2.top());
				s2.pop();
			}
			//開始計算結果
			while (s1.size())
			{
				auto obj = s1.top();
				s1.pop();
				switch (obj.type())
				{
				case TYPE_NUMBER://如果是數字,則放入S2
					s2.push(obj);
					break;
				case TYPE_OPERATOR://如果是運算符,則從s2中取兩個數來計算
				{
					if (s2.size() < 2)
					{
						//出錯了,s2中的數不足兩個
						return nullptr;
					}
					auto numb2 = s2.top();	//先出來的是第二個運算值
					s2.pop();
					auto numb1 = s2.top();
					s2.pop();
					//根據運算符,進行計算
					switch (obj.op())
					{
					case '+':
						s2.push(SourceElement(numb1.value() + numb2.value()));
						break;
					case '-':
						s2.push(SourceElement(numb1.value() - numb2.value()));
						break;
					case '*':
						s2.push(SourceElement(numb1.value() * numb2.value()));
						break;
					case '/':
						if (abs(numb2.value()) < 0.00000001){
							//出錯了,被除數不能為零
							return nullptr;
						}
						s2.push(SourceElement(numb1.value() / numb2.value()));
						break;
					default:
						//出錯了,這裡不應該出現四則運算外的運算符
						return nullptr;
						break;
					}
				}
					break;
				default:
					//出錯了,這裡不應該出現數字和運算符外其它的類型
					return nullptr;
				}
				
			}
			if (s2.size() != 1)
			{
				//出錯了,合法的計算結果裡,此時應該隻有一個值
				return nullptr;
			}
			return std::shared_ptr<double>(new double(s2.top().value()));
		}
	};
}
           

繼續閱讀