四則運算表達式解析和求值(支持括號、小數)


需求分析
ios

四則運算表達式的解析和求值從概念上看是一個邏輯很清晰的過程。git

遵循了左結合、優先級差別和括號能夠提高優先級這三條規則。express


實現思路
c#

實際上只要遍歷表達式,將小數和運算符分離到兩個序列容器中,在遍歷的過程當中,考慮左結合和括號對優先級的影響,將相關信息和運算符記錄在一塊兒,而後在遍歷結束後,按照優先順序從高到底,從小數序列中拉取兩個元素(多是中間結果的二叉樹,實際上小數是一種特例,是左右子樹爲空的解析完成的二叉樹),而且把運算符的兩棵子樹指向兩個元素,把這棵新樹放回到小數容器中。不斷重複這個過程直到運算符序列爲空,這時小數序列中就只剩下單一的二叉樹根節點,它是完整的解析結果。post

對二叉樹的求值相對容易,這裏直接用節省代碼的遞歸完成。lua


預處理spa

表達式的解析就不贅述了,它先於上述邏輯完成預處理的過程,就是純粹的體力勞動。code

這裏用一個Token類實現主要的邏輯。主要是小數被解析成一箇中間的Token結構,在其中保存float類型的小數數值。orm

Formula.h遞歸

#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
using namespace std;

class FloatParser
{
	public:
		static const char* Parse(const char*  p_cursor, float& p_num)
		{
			p_cursor = ParsePureNum(p_cursor, p_num);
			
			if (*p_cursor != '.')
			{
				return p_cursor;
			}
			else
			{	
				p_cursor++;
				float numPostDot = 0;
				p_cursor = ParsePureNum(p_cursor, numPostDot, true);
				if (p_cursor)
				{
					p_num += numPostDot;
					return p_cursor;
				}
				else
				{
					return NULL;
				}
			}			
		}
		
	private:
		static const char* ParsePureNum(const char*  p_cursor, float& p_num, bool bPostDot = false)
		{			
			int i;
			for ( i = 0, p_num = 0; ; p_cursor++, i++ )
			{
				char ch = *p_cursor;

				// reaches end of input EOF
				if (ch == 0)
				{
					if ( i == 0)
						return NULL;
					else
						break;
				}

				// skip all spaces, leading and traling
				if (ch == ' ')
				{
					--i;
					continue;
				}
				
				// accumulate digits
				if (ch >= '0' && ch <= '9')
				{
					p_num = 10 * p_num + (ch - '0');
					continue;
				}

				// other characters, including dot, stop parsing
				break;
			}
			
			// if post dot digits, parse 9999 into 0.9999, where i is effective number of digits, in 9999 case namely 4
			if (bPostDot)
			{
				for(; i > 0; i--)
				{
					p_num /= 10;
				}
			}
			return p_cursor;
		}
};


enum EToken
{
	eTokenNone,

	eTokenNum,

	eTokenPlus,	
	eTokenMinus,
	eTokenMul,
	eTokenDiv,

	eTokenParLeft,
	eTokenParRight,
};

class Token
{
	public:
		Token(){}
		Token(float p_num):m_eToken(eTokenNum), m_num(p_num){}

	public:
		static const char* ParseOne(const char* p_cursor, Token& p_token)
		{
start:
			switch (*p_cursor)
			{
				case ' ':
					do
					{
						++p_cursor;
					}while(*p_cursor == ' ');
					goto start;
				case 0:
					return NULL;
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '.':
					p_token.m_eToken = eTokenNum;
					return FloatParser::Parse(p_cursor, p_token.m_num);
				case '+':
					p_token.m_eToken = eTokenPlus;
					break;
				case '-':
					p_token.m_eToken = eTokenMinus;
					break;
				case '*':
					p_token.m_eToken = eTokenMul;
					break;
				case '/':
					p_token.m_eToken = eTokenDiv;
					break;
				case '(':
					p_token.m_eToken = eTokenParLeft;
					break;
				case ')':
					p_token.m_eToken = eTokenParRight;
					break;
				default:
					return NULL;
			}

			return ++p_cursor;
		}

	public:
		EToken m_eToken;
		float m_num;	
};

//int testFloatParser()
//{
//	float num;
//	FloatParser::Parse(".9090",num);
//	cout << num << endl;
//}

class Tree
{
	public:
		char GetOpChar()const
		{
			switch(m_eToken)
			{
				case eTokenPlus:
					return '+';
				case eTokenMinus:
					return '-';
				case eTokenMul:
					return '*';
				case eTokenDiv:
					return '/';
			}
			return 'x';
		}
	private:
		typedef float (*FUNC)(float op1, float op2);

	public:
		Tree():
		m_eToken(eTokenNone),
		m_pLeft(NULL),
		m_pRight(NULL),
		m_num(0),
		m_level(0),
		m_indexOnLevel(0)
		{
		}

		float Evaluate()
		{
			cout << "Evaluating " << GetOpChar() << ":" << m_num << endl;
			switch(m_eToken)	
			{
				case eTokenNum:
					return m_num;

				case eTokenPlus:
				case eTokenMinus:
				case eTokenMul:
				case eTokenDiv:
					return (Tree::GetFunc(m_eToken))(m_pLeft->Evaluate(), m_pRight->Evaluate());

				default:
					return -3.14;
			}
		}


public:
		int GetOpPriorty()const
		{
			switch(m_eToken)
			{
				case eTokenPlus:
				case eTokenMinus:
					return 0;

				case eTokenMul:
				case eTokenDiv:
					return 1;
			}

			return -1;
		}

		static Tree* MakeTreeByToken(Token token)
		{
			Tree * tree = new Tree;

			tree->m_eToken = token.m_eToken;
			tree->m_num = token.m_num;

			return tree;
		}

		static Tree* MakeTreeWithTokens(const vector<Token> tokens);
		

	private:
		static float funcPlus(float op1, float op2)
		{
			cout << op1 << "+" << op2 << endl;
			return op1 + op2;
		}

		static float funcMinus(float op1, float op2)
		{
			cout << op1 << "-" << op2 << endl;
			return op1 - op2;
		}

		static float funcMul(float op1, float op2)
		{
			cout << op1 << "*" << op2 << endl;
			return op1 * op2;
		}

		static float funcDiv(float op1, float op2)
		{
			cout << op1 << "/" << op2 << endl;
			return op1 / op2;
		}

		static FUNC GetFunc(EToken eToken)
		{
			switch(eToken)
			{
				case eTokenPlus:
					return funcPlus;
				case eTokenMinus:
					return funcMinus;
				case eTokenMul:
					return funcMul;
				case eTokenDiv:
					return funcDiv;
				default:
					return NULL;
			}
		}



	private:
		EToken m_eToken;
		float m_num;

		Tree *m_pLeft;
		Tree *m_pRight;

		int m_level;
		int m_indexOnLevel;

	friend class BiggerBetter;
};


Formula.cpp

#include "Formula.h"

struct BiggerBetter
{
	bool operator()(Tree* op1, Tree* op2)
	{
		int nLevelDiff = op1->m_level - op2->m_level;
		int nOpDiff = op1->GetOpPriorty() - op2->GetOpPriorty();
		int nIndexDiff = -(op1->m_indexOnLevel - op2->m_indexOnLevel);

		if (nLevelDiff != 0)
			return nLevelDiff < 0;

		if (nOpDiff != 0)
			return nOpDiff < 0;

		if (nIndexDiff != 0)
			return nIndexDiff < 0;

		return false;
	}
};

Tree* Tree::MakeTreeWithTokens(const vector<Token> tokens) 
{   
	vector<Tree*> trees;
	vector<Tree*> ops;
	priority_queue<Tree*, vector<Tree*>, BiggerBetter> opsPriority;

	stack<int> indexs;

	int level = 0;
	int indexOnLevel = 0;

	for(int i = 0; i < tokens.size(); i++)
	{   
		const Token& token = tokens[i];

		Tree* tree = Tree::MakeTreeByToken(token);

		switch(tree->m_eToken)
		{   
			case eTokenNum:
				// push num into list
				trees.push_back(tree);
				break;

			case eTokenPlus:
			case eTokenMinus:
			case eTokenMul:
			case eTokenDiv:

				tree->m_level = level;
				tree->m_indexOnLevel = indexOnLevel++;

				// push op into list
				ops.push_back(tree);
				// push op into priority list
				opsPriority.push(tree);
				break;

			case eTokenParLeft:
				level++;
				indexs.push(indexOnLevel);
				indexOnLevel = 0;
				break;

			case eTokenParRight:
				level--;
				if(indexs.empty())
				{   
					for(int i = 0; i < trees.size(); i++)
						delete trees[i];
					for(int i = 0; i < ops.size(); i++)
						delete ops[i];
					// Parse Fail return NULL
					return NULL;
				}   

				indexOnLevel = indexs.top();
				indexs.pop();
				break;
		}   
	} 

	while(!opsPriority.empty())
	{
		// get opTree with highest priority, remove it from priority queue
		Tree * opTree = opsPriority.top();
		cout << "opTree: " << opTree->GetOpChar() << endl;
		opsPriority.pop();

		// index the opTree in trees, remove opTree from ops vector
		vector<Tree*>::iterator it = find(ops.begin(),  ops.end(), opTree);
		int opIndex = it - ops.begin();
		//remove(ops.begin(), ops.end(), opTree);
		ops.erase(it);

		// get tree1 and tree2 by opIndex
		Tree *tree1 = trees[opIndex];
		Tree *tree2 = trees[opIndex+1];

		// set tree1 and tree2 to left and rigth children of opTree
		opTree->m_pLeft  = tree1;
		opTree->m_pRight = tree2;

		// insert opTree to tree, before tree1's position
		vector<Tree*>::iterator insertIt = find(trees.begin(), trees.end(), tree1);
		int nInsertPos = insertIt - trees.begin();
		trees.insert(insertIt, opTree);
		// remove tree1 and tree2 from trees vector
		trees.erase(trees.begin() + nInsertPos+1);
		trees.erase(trees.begin() + nInsertPos+1);
		//remove(trees.begin(), trees.end(), tree1);
		//remove(trees.begin(), trees.end(), tree2);
	}

	// return the finished tree root
	cout << trees.size() << endl;
	return trees[0];
}


main.cpp

#include "Formula.h"

#include <cstring>

int main()
{
	while(true){
		cout << "Input the expression: ";

		string str;
		cin >> str;

		vector<Token> tokens;

		Token token;
		//const char* input = "(1+2)*(3.14-4)/(5-6-7-8-9*90)";
		const char* input = str.c_str();

		while(input && *input)
		{
			input = Token::ParseOne(input, token);

			if (!input)
				break;

			tokens.push_back(token);

			cout << "token:" << token.m_eToken  << endl;
		}

		if (!input)
		{
			cout << "Parse broken" << endl;
		}
		else
		{
			cout << "Parse finished" << endl;

			// create formula tree with tokens
			Tree* tree = Tree::MakeTreeWithTokens(tokens);
			float num = tree->Evaluate();
			cout << num << endl;
		}
	}
}
相關文章
相關標籤/搜索