四则运算表达式解析和求值(支持括号、小数、小数正负号)



需求分析

四则运算表达式的解析和求值从概念上看是一个逻辑很清晰的过程。

遵循了左结合、优先级差异和括号可以提升优先级这三条规则。


实现思路

实际上只要遍历表达式,将小数和运算符分离到两个序列容器中,在遍历的过程中,考虑左结合和括号对优先级的影响,将相关信息和运算符记录在一起,然后在遍历结束后,按照优先顺序从高到底,从小数序列中拉取两个元素(可能是中间结果的二叉树,实际上小数是一种特例,是左右子树为空的解析完成的二叉树),并且把运算符的两棵子树指向两个元素,把这棵新树放回到小数容器中。不断重复这个过程直到运算符序列为空,这时小数序列中就只剩下单一的二叉树根节点,它是完整的解析结果。

对二叉树的求值相对容易,这里直接用节省代码的递归完成。

预处理

表达式的解析就不赘述了,它先于上述逻辑完成预处理的过程,就是纯粹的体力劳动。

这里用一个Token类实现主要的逻辑。主要是小数被解析成一个中间的Token结构,在其中保存float类型的小数数值。

Formula.h

#ifndef _FORMULA_H
#define _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)
		{
			float sign = 1;
			p_cursor = ParsePureNum(p_cursor, p_num, sign);

			if (*p_cursor != ‘.‘)
			{
				// apply sign
				p_num *= sign;

				return p_cursor;
			}
			else
			{
				p_cursor++;
				float numPostDot = 0;
				p_cursor = ParsePureNum(p_cursor, numPostDot, sign, true);
				if (p_cursor)
				{
					p_num += numPostDot;

					// apply sign
					p_num *= sign;

					return p_cursor;
				}
				else
				{
					return NULL;
				}
			}
		}

	private:
		static const char* ParsePureNum(const char*  p_cursor, float& p_num, float& p_sign, bool bPostDot = false)
		{
			bool bPermitSign = !bPostDot;
			p_sign = 1;

			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;
				}

				// record sign
				if (bPermitSign && (ch == ‘-‘ || ch == ‘+‘))
				{
					--i;

					switch(ch)
					{
					case ‘-‘:
						p_sign = -1;
						break;
					case ‘+‘:
						p_sign = 1;
						break;
					}

					continue;
				}

				bPermitSign = false;				

				// 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 ‘+‘:
					if ( *(p_cursor+1) >= ‘0‘ && *(p_cursor+1) <= ‘9‘ )
					{
						p_token.m_eToken = eTokenNum;
						return FloatParser::Parse(p_cursor, p_token.m_num);
					}
					else
					{
						p_token.m_eToken = eTokenPlus;
						break;
					}

				case ‘-‘:
					if ( *(p_cursor+1) >= ‘0‘ && *(p_cursor+1) <= ‘9‘ )
					{
						p_token.m_eToken = eTokenNum;
						return FloatParser::Parse(p_cursor, p_token.m_num);
					}
					else
					{
						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;
};

#endif

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: ";

		char buffer[512];
		cin.getline(buffer,sizeof(buffer));
		string str(buffer);

		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;
		}   
	}   
}
时间: 2024-10-13 17:08:08

四则运算表达式解析和求值(支持括号、小数、小数正负号)的相关文章

条件表达式的短路求值与函数的延迟求值

延迟求值是 .NET的一个很重要的特性,在LISP语言,这个特性是依靠宏来完成的,在C,C++,可以通过函数指针来完成,而在.NET,它是靠委托来完成的.如果不明白什么是延迟求值的同学,我们先看看下面的一段代码: static void TestDelayFunction() { TestDelayFunton1(true,trueFun3); } static void TestDelayFunton1(bool flag , Func<bool> fun ) { if(flag) fun(

Go 表达式求值器

示例:表达式求值器 本篇将创建简单算术表达式的一个求值器. 定义接口和类型 开始,先确定要使用一个接口 Expr 来代表这种语言的任意一个表达式.暂时没有任何方法,稍后再逐个添加: // Expr: 算术表达式 type Expr interface{} 我们的表达式语言将包括以下符号: 浮点数字面量 二元操作符:加减乘除(+.-.*.\/) 一元操作符:表示正数和负数的 -x 和 +x 函数调用:pow(x,y).sin(x) 和 sqrt(x) 变量:比如 x.pi,自己定义一个变量名称,每

刁肥宅详解中缀表达式求值问题:C++实现顺序/链栈解决

1. 表达式的种类 如何将表达式翻译成能够正确求值的指令序列,是语言处理程序要解决的基本问题,作为栈的应用事例,下面介绍表达式的求值过程. 任何一个表达式都是由操作数(亦称运算对象).操作符(亦称运算符)和分界符组成的.通常,算术表达式有3种表示: ①中缀(infix)表示:<操作数><操作符><操作数>,如A+B. ②前缀(prefix)表示: <操作符><操作数><操作数>,如+AB. ③后缀(postfix)表示: <操作

前缀、中缀、后缀表达式及其求值

它们都是对表达式的记法,因此也被称为前缀记法.中缀记法和后缀记法.它们之间的区别在于运算符相对与操作数的位置不同:前缀表达式的运算符位于与其相关的操作数之前:中缀和后缀同理. 比如: (4 + 5) × 6- 7 就是中缀表达式 - × + 4567 前缀表达式 45 + 6×7 - 后缀表达式 中缀表达式(中缀记法) 中缀表达式是一种通用的算术或逻辑公式表示方法,操作符以中缀形式处于操作数的中间.中缀表达式是人们常用的算术表示方法. 虽然人的大脑很容易理解与分析中缀表达式,但对计算机来说中缀表

中缀表达式求值问题

           中缀表达式求值问题 中缀表达式的求值问题是一个比较常见的问题之一,我们通常在编写程序时,直接写出表达式让编译器去处理,很少去关心编译器是怎么对表达式进行求值的,今天我们来一起了解一下其中具体的原理和过程. 表达式一般来说有三种:前缀表达式.中缀表达式.后缀表达式,其中后缀表达式又叫做逆波兰表达式.中缀表达式是最符合人们思维方式的一种表达式,顾名思义,就是操作符在操作数的中间.而前缀表达式和后缀表达式中操作符分别在操作数的前面和操作数的后面.举个例子: 3+2 这个是最简单的

QT之计算器对四则运算表达式的解析

前面我们已经关于计算器介绍的已经够多了,那么它现在还是没有具备计算的功能. 今天我们来继续讲解计算器的解析算法,那么对于一个四则运算表达式, 它是如何读懂的呢?比如:"+9.11 + ( -3 - 1 ) * -5 ": 人类习惯的数学表达式叫做中缀表达式,还有一种将运算符放在数字后面的后缀表达式, 比如:5 + 3 ==> 5 3 +: 1 + 2 * 3 ==> 1 2 3 * +;像这种就是后缀表达式. 那么中缀表达式是符合人类的阅读和思维习惯,后缀表达式则符合计算机

序列点在C语言表达式求值中的作用

摘要: 本文开创性地分析了序列点在C语言表达式求值中的作用:序列点左边的操作数要先于其右边的操作数求值.讨论了逗号操作符,.逻辑与操作符&&.逻辑或操作符||和条件操作符?:的问号处需要序列点的原因.举例分析了序列点在表达式求值中的作用. 关键字:序列点 表达式求值 C语言 C语言作为一种主流程序设计语言,许多编程语言如Java.C++.C#都借鉴了它的语法.C语言也是一种很适当的程序设计入门的教学语言,国内大专院校的许多专业都开设了这门课程并且大多将其作为第一门程序设计语言课程,同时C语

C++求值顺序

<C++Primer5th>中文版第124页 C++语言没有明确规定大多数二元运算符的求值顺序, 给编译器优化留下了余地. 这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,这个是否可以接受? 1.首先可以知道优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值. 比如: int i=f1()*f2(); 在这里虽然f1和f2在乘法之前被调用,但是f1先调用还是f2先调用却不得而知. 2.再比如结合律: -- int i=0; cout<<i<<

表达式求值:从“加减”到“带括号的加减乘除”的实践过程

本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee ● 为什么想做一个表达式求值的程序 最近有一个需求,策划想设置游戏关卡的某些数值,这个数值不是一个常量,而是根据关卡的某些环境数据套上一个计算表达式算出来的.这个需求无法用excel拖表预计算出,因为关卡的环境数据只有在游戏中才能产生,在excel制表时,这些都是未知的.作为程序员,我可以把计算表达式硬编码在代码中,但这个做法有缺陷,如果策划要修改计算表达式的话,只能通过我修改程序并