Simple Equation Solver ( in C++ )

Alex Edwards 1 Tallied Votes 8K Views Share

This is a remake of the Simple Equation Solver snippet that is written in Java.

This is an equivalent version written in C++.

/**
 * Numerics.cpp
 */
#ifndef NUMERICS
#define NUMERICS

#include <sstream>
#include <string>

using std::stringstream;
using std::string;

/**
 * Convenience class for enabling easy
 * conversions between strings and primitive types.
 *
 * This class is not meant to support non-primitive types,
 * but it should if target class Type has istream and ostream
 * operators overloaded.
 */
namespace Numerics{

	template<class T>
	class Numerical{
	
		private:
			T number;

		public:
			Numerical(T value = 0) : number(value){}
			Numerical(const string& arg){
				const_cast<Numerical&>(*this)(arg);
			}

			/**
			 * Attempts to assign the argument value to the value
			 * of this Object's type.
			 * If the value is invalid, nothing happens.
			 */
			string operator()(const string& arg){
				stringstream ss (stringstream::in | stringstream::out);
				try{
					ss << arg;
					ss >> number;
				}catch(...){
					// currently unsupported
				}
				return ss.str();
			}

			/**
			 * Attempts to assign the argument value to the value of
			 * this Object's type.
			 */
			T operator()(T value){
				return (number = value);
			}

			/**
			 * Returns a string representation of this Object's number
			 */
			string getString(){
				stringstream ss (stringstream::in | stringstream::out);
				ss << number;
				return ss.str();
			}

			/**
			 * Returns a copy of this Object's number
			 */
			T getValue(){
				return number;
			}

			/**
			 * Extraction operator used to return the underlying value
			 * during operations assosciated with primitive types.
			 */
			operator T& (){
				return number;
			}

			/**
			 * const version of the above operator. Returns a copy
			 * of this Object's number.
			 */
			operator T () const{
				return number;
			}
	};

	/* Some meaningful typedefs for Numerical types */
	typedef Numerical<short> Short;
	typedef Numerical<unsigned short> U_Short;
	typedef Numerical<int> Integer;
	typedef Numerical<unsigned int> U_Integer;
	typedef Numerical<double> Double;
	typedef Numerical<float> Float;
	typedef Numerical<char> Byte;
	typedef Numerical<unsigned char> U_Byte;
	typedef Numerical<wchar_t> Wide_Byte;
	typedef Numerical<long int> Long;
	typedef Numerical<unsigned long int> U_Long;

	/* For non-standard types, like __int8, __int16, __int32, and __int64 */
	#ifdef ALLOW_NONSTANDARD_PRIMITIVE_TYPES

		#if (ALLOW_NONSTANDARD_PRIMITIVE_TYPES == 0x01)
			typedef Numerical < __int8 > __Int8;
			typedef Numerical < unsigned __int8 > U___Int8;
			typedef Numerical < __int16 > __Int16;
			typedef Numerical < unsigned __int16 > U___Int16;
			typedef Numerical < __int32 > __Int32;
			typedef Numerical < unsigned __int32 > U___Int32;
			typedef Numerical < __int64 > __Int64;
			typedef Numerical < unsigned __int64 > U___Int64;
		#endif

	#endif
}

#endif


////////////////////////////////////

/**
 * EquationSolver.h
 */

#ifndef EQUATION_SOLVER_H
#define EQUATION_SOLVER_H

#include <string>
#include <vector>

using std::string;
using std::vector;

namespace EquationHelper{

	class EquationSolver{
		private: 
			EquationSolver();
			static string doOperation(const string&, char, const string&);
			static void correctedString(string&);
			static void removeSpaces(string&);
			static string parse(const string&);
			static bool isSolvable(const string&);
			static void calculate(vector<string>&, vector<char>&, const string&);

		public:
			static string solve(const string&, int = 50);
	};
}
#include "EquationSolver.cpp"

#endif

////////////////////////////////

/**
 * EquationSolver.cpp
 */

#ifdef EQUATION_SOLVER_H

#include <iostream>
#include <cmath>
#include <vector>
#include <string>
#include "Numerics.cpp"

using namespace EquationHelper;
using namespace Numerics;
using std::size_t;
using std::vector;
using std::string;
using std::cout;
using std::endl;
using std::ios;

typedef EquationSolver ES;

/**
 * Private constructor - does nothing.
 */
ES::EquationSolver(){}

/**
 * Performs the specified operation against the
 * argument strings. The operation is dependant on
 * the value of op.
 */
string ES::doOperation(const string& lhs, char op, const string& rhs){
	
	Double bdLhs = lhs;
	Double bdRhs = rhs;
	Double temp;
	switch(op){
		case '^':
			temp( pow( bdLhs, bdRhs ) );
			break;
		case '*':
			temp( bdLhs * bdRhs );
			break;
		case '/':
			temp( bdLhs / bdRhs );
			break;
		case '+':
			temp( bdLhs + bdRhs );
			break;
		case '%':
			temp( fmod(bdLhs, bdRhs) );
			break;
	}
	return temp.getString();
}

/**
 * Returns the string with its enclosing paranthesis
 * stripped from it.
 */
void ES::correctedString(string& arg){
	
	size_t pos1 = arg.find_first_of("(");
	size_t pos2 = arg.find_last_of(")");

	if(pos1 >= 0 && pos1 < arg.length() && pos2 >= 0 && pos2 <= arg.length())
		arg[pos1] = arg[pos2] = ' ';
}

/**
 * Remove spaces from the argument string.
 */
void ES::removeSpaces(string& argu){

	string temp = "";
	for(size_t i = 0; i < argu.length(); i++)
		if(argu[i] != ' ')
			temp += argu[i];
	argu = temp;
}

/**
 * The brains of the program.
 * Solves expressions by using recursion for complex expressions.
 */
string ES::parse(const string& param){
	
	string expression = param;
	correctedString(expression);
	removeSpaces(expression);
	string finalExpression = "";

	bool operatorEncountered = true;
	for(size_t i = 0; i < expression.length(); i++){
		if(expression[i] == '('){	
			string placeHolder = "(";	
			int valuesCounted = 1;		
			operatorEncountered = false;
			for(size_t j = i + 1; valuesCounted != 0; j++){	
				if(expression[j] == '(')	
					valuesCounted++;
				else if(expression[j] == ')')	
					valuesCounted--;

				placeHolder += expression[j];
			}

			string evaluatedString = parse(placeHolder); 
			finalExpression += evaluatedString;	
			i += (placeHolder.length() - 1); 
		}else{
			if(expression[i] == '-' && operatorEncountered == false)
				finalExpression += '+';

			finalExpression += expression[i];
			if((expression[i] == '+'
			|| expression[i] == '/'
			|| expression[i] == '^'
			|| expression[i] == '*'
			|| expression[i] == '%'
			|| expression[i] == '-'))
				operatorEncountered = true;
			else if(expression[i] != ' ')
				operatorEncountered = false;
		}
	}

	removeSpaces(finalExpression);	
	string perfectExpression = "";	

	for(size_t i = 0; i < finalExpression.length(); i++){	
		if((i + 1) < finalExpression.length())	
			if(finalExpression[i] == '-' && finalExpression[i + 1] == '-')
				i += 2;																	
		perfectExpression += finalExpression[i];	
	}
	finalExpression = perfectExpression;

	vector<string> totalNumbers;
	vector<char> totalOperations;	
	cout << finalExpression << endl;	

	for(size_t i = 0; i < finalExpression.length(); i++){
		if(finalExpression[i] >= '0' && finalExpression[i] <= '9'
		|| finalExpression[i] == '-' || finalExpression[i] == '.'){	
			string temp = "";	//
			for(size_t j = i; j < finalExpression.length(); j++){	
				if(finalExpression[j] >= '0' && finalExpression[j] <= '9'
				|| finalExpression[j] == '-' || finalExpression[j] == '.'){
						temp += finalExpression[j];	
				}else break;
			}
			totalNumbers.push_back(temp);	
			i += temp.length() == 0 ? 0 : (temp.length() - 1);	
		}else if(finalExpression[i] == '*'
			  || finalExpression[i] == '/'
			  || finalExpression[i] == '^'
			  || finalExpression[i] == '+'
			  || finalExpression[i] == '%'
			  ){
			totalOperations.push_back(finalExpression[i]);
		}
	}

	ES::calculate(totalNumbers, totalOperations, "^");
	ES::calculate(totalNumbers, totalOperations, "*/%");
	ES::calculate(totalNumbers, totalOperations, "+");

	return totalNumbers[0];
}

/**
 * Calculates the numbers in the first vector using the operands in the 2nd vector,
 * based on the expressions allowed which are determined by the string argument.
 */
void ES::calculate(vector<string>& totalNumbers, vector<char>& totalOperations, 
				   const string& arg){

	for(int i = 0; i < static_cast<int>(totalOperations.size()); i++){
		if( arg.find(totalOperations[i]) != arg.npos){
			totalNumbers[i] = doOperation(totalNumbers[i], totalOperations[i], totalNumbers[i + 1]);
			
			size_t oldNumberLength = totalNumbers.size();
			size_t oldOperatorLength = totalOperations.size();
			size_t nextNumberLength = oldNumberLength - 1;
			size_t nextOperatorLength = oldOperatorLength - 1;
			size_t sCount = 0;
			size_t oCount = 0;
			vector<string> temp1 ( nextNumberLength );
			vector<char> temp2 ( nextOperatorLength );
			
			for(size_t j = 0; j < oldNumberLength; j++){
				if(j != static_cast<int>(i + 1)){
					temp1[sCount++] = totalNumbers[j];
				}
				if(j != i && j < oldOperatorLength){
					temp2[oCount++] = totalOperations[j];
				}
			}
			totalNumbers = temp1;
			totalOperations = temp2;

			i--;
		}
	}
}

/**
 * Returns true if the equation is solvable (not really),
 * returns false otherwise.
 *
 * This function is truly a misnomer, because more restrictions
 * should be put in place.
 */
bool ES::isSolvable(const string& eq){
	
	int paranthesisCount = 0;
	for(size_t i = 0; i < eq.length(); i++){
		if(eq[i] == '(')	
			paranthesisCount++;	
		else if(eq[i] == ')')	
			paranthesisCount--;	

		if(paranthesisCount < 0)
			return false;
	}
	return paranthesisCount == 0;
}

/**
 * An attempt to solve a string-expression, given
 * a precision value.
 */
string ES::solve(const string& eq, int prec){

	if(isSolvable(eq)){

		stringstream ss (stringstream::in | stringstream::out);
		cout << eq << endl;
		string value;

		value += '(';
		value += eq;
		value += ')';
		
		ss.setf(0, ios::floatfield);
		ss.precision(prec);
		ss << parse(value);

		return ss.str();
	}else return "";
}

#endif

////////////////////////////////////

/**
 * DriverProgram.cpp
 */

/****************************************
 * @Author: Mark Alexander Edwards Jr.
 *
 * This program uses a utility class to solve
 * complex equations represented by string 
 * expressions
 ****************************************/
#include <iostream>
#include <ctime>
#include <string>
#include "Numerics.cpp"
#include "EquationSolver.h"

using std::cin;
using std::cout;
using std::endl;
using std::flush;
using std::string;
using namespace Numerics;
using namespace EquationHelper;

int main(){
	
	cout << ES::solve("5 + 3 * (8 - 4)") << endl << endl;
	cout << ES::solve("12 / (3 * 4) + 5") << endl << endl;

	while(true){
		cout << "Enter an equation you would \nlike to solve (enter 'exit' to quit): " << flush;
		try{
			string temp;
			getline(cin, temp);
			if(temp.compare("exit") == 0) exit(0);
			clock_t t1 = clock();
			cout << "Answer: " + ES::solve(temp, 4) << endl;
			clock_t t2 = clock();
			cout << "\nTime taken to calculate value: " <<
				(t2-t1) << " Clock cycles" <<endl;
		}catch(...){
			cout << "Invalid expression! Please try again!" << endl;
		}
	}

	cin.ignore();
	cin.get();
	return 0;
}
William Hemsworth 1,339 Posting Virtuoso

Very nice :)

Alex Edwards 321 Posting Shark

When I have more time I might refactor the program to accept manipulators like cos, sin, tan, etc as well as SQRT but then again that can be accomplished by taking values to the .5 power @_@

Alex Edwards 321 Posting Shark

Apparently there's a problem with the current program @_@

According to VernonDozier, Algebraic law states that exponents are evaluated in a R-to-L fashion and not L-to-R.

The fix is easy to produce, but introducing negative multiplication after exponential calculation may indeed be difficult unless I re-evaluate the entire system and determine how to conveniently change the implementation.

ddanbe 2,724 Professional Procrastinator Featured Poster

Just browsing the code I wondered why there is no case '-' in string ES::doOperation(const string& lhs, char op, const string& rhs){...

Alex Edwards 321 Posting Shark

Subtraction is technically the same as adding a negative, so there was no need to add a subtraction operator to the doOperation method. Conversions from subtraction to adding a negative are most likely done during the expansion/compression portion of the parse method.

ddanbe 2,724 Professional Procrastinator Featured Poster

Thanks for the explanation :o)

hassan_24 0 Newbie Poster

I tried this code of your Mr. Alexander and i tried some valid random strings/equations though it tried to solve it but failed the answer was totally wrong in the calculations and it also does not show the correct answer for other string larger than 5-6 operator also containing "()" and it fails to evaluate it when the string length exceeds more than 3 parts which it usually breaks down the input string/equations... i love it if you review it and make the corrections ... Regards

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.