3
\$\begingroup\$

I have created a console program where users type in a math equation such as 5+4-6 or (5+16(8-4))/16 that follows order of operations. The equation is sent to a char array that is then parsed. If a # or . is encountered, the char is placed into a buffer. The buffer is tokenized into a token class, followed by the operator itself being tokenized. The program works beautifully and as expected. Exceptions and errors still need to be handled where right now it is only writing to the console and continuing.

What I am looking to do next is add in higher functions, more in-line with a scientific calculator, starting with the constants such as e and pi, then moving into trig functions. It is in handling the functions that I am having an issue with.

Would you recommend storing the chars in a buffer similar to the #'s and treat them as a function if a ) is found immediately following, and a constant otherwise? What would you suggest? Also, what could I improve upon?

PROGRAM.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CalcTest.Core;
namespace CalcTest
{
 class Program
 {
 static Calculator calculator;
 static void Main(string[] args)
 {
 calculator = new Calculator();
 calculator.Entry();
 }
 }
}

ENUMS.CS

using CalcTest;
using CalcTest.Core;
namespace CalcTest.Core
{
 public enum TokenType
 {
 Value,
 Operand,
 Function,
 Paramater,
 Variable
 }
}

TOKEN.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalcTest.Core
{
 public class Token
 {
 private TokenType type;
 private string val;
 public TokenType TYPE
 {
 get { return type; }
 set { type = value; }
 }
 public string VALUE
 {
 get { return val; }
 set { val = value; }
 }
 public Token(TokenType t, string s)
 {
 this.TYPE = t;
 this.VALUE = s;
 //Write();
 }
 public override string ToString()
 {
 return String.Format("Token:= Type: {0}; Value: {1}", TYPE.ToString(), VALUE);
 }
 private void Write()
 {
 Console.WriteLine(this.ToString());
 }
 }
}

CALCULATOR.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalcTest.Core
{
 public class Calculator
 {
 private Stack<Token> tokenStack;
 private Queue<Token> tokenQue;
 private char[] buffer;
 private int bufferLoc;
 private TokenType? priorType;
 public Calculator() 
 {
 tokenQue = new Queue<Token>();
 tokenStack = new Stack<Token>();
 buffer = new char[100];
 bufferLoc = 0;
 priorType = null;
 }
 #region BasicFunctions
 private Token Addition(Token t1, Token t2)
 {
 //Console.WriteLine("Addition:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
 double arg1 = double.Parse(t1.VALUE);
 double arg2 = double.Parse(t2.VALUE);
 double sum = arg1 + arg2;
 return CreateToken(TokenType.Value, sum.ToString());
 }//end Addition
 private Token Subtract(Token t1, Token t2)
 {
 //Console.WriteLine("Subtraction:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
 double arg1 = double.Parse(t1.VALUE);
 double arg2 = double.Parse(t2.VALUE);
 double sub = arg1 - arg2;
 return CreateToken(TokenType.Value, sub.ToString());
 }//end Subtract
 private Token Multiplication(Token t1, Token t2)
 {
 //Console.WriteLine("Multiplication:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
 double arg1 = double.Parse(t1.VALUE);
 double arg2 = double.Parse(t2.VALUE);
 double multi = arg1 * arg2;
 return CreateToken(TokenType.Value, multi.ToString());
 }//end Multiplication
 private Token Division(Token t1, Token t2)
 {
 //Console.WriteLine("Division:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
 double arg1 = double.Parse(t1.VALUE);
 double arg2 = double.Parse(t2.VALUE);
 double div = arg1/arg2;
 return CreateToken(TokenType.Value, div.ToString());
 }//end Division
 private Token Power(Token t1, Token t2)
 {
 //Console.WriteLine("Power:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
 double arg1 = double.Parse(t1.VALUE);
 double arg2 = double.Parse(t2.VALUE);
 double power = Math.Pow(arg1,arg2);
 return CreateToken(TokenType.Value, power.ToString());
 }//end Power
 #endregion
 public void Entry()
 {
 char[] equation = new char[1];
 while (true)
 {
 Array.Clear(equation, 0, equation.Length);
 equation = Console.ReadLine().ToCharArray();
 ParseEquation(equation);
 foreach (var t in tokenQue)
 {
 Console.Write(t.VALUE + ", ");
 }
 Console.WriteLine();
 EvalEquation();
 if (tokenStack.Count > 0)
 Console.WriteLine(tokenStack.Pop().VALUE);
 else
 Console.WriteLine("Nothing returned");
 tokenStack.Clear();
 tokenQue.Clear();
 }
 }//end Entry
 #region EvalFunctions
 private void EvalEquation()
 {
 Token temp = null;
 while (tokenQue.Count != 0)
 {
 temp = tokenQue.Dequeue();
 if (temp.TYPE == TokenType.Value)
 tokenStack.Push(temp);
 else
 OpperationSwitch(temp);
 }
 }//end EvalEquation
 private void OpperationSwitch(Token t)
 {
 Token ret = null;
 if (tokenStack.Count >= 2)
 {
 Token t2 = tokenStack.Pop();
 Token t1 = tokenStack.Pop();
 switch (t.VALUE)
 {
 case ("*"):
 ret = Multiplication(t1, t2);
 break;
 case ("/"):
 ret = Division(t1, t2);
 break;
 case ("+"):
 ret = Addition(t1, t2);
 break;
 case ("-"):
 ret = Subtract(t1, t2);
 break;
 case ("^"):
 ret = Power(t1, t2);
 break;
 }
 if (ret != null)
 tokenStack.Push(ret);
 }
 else
 {
 Console.WriteLine("Error in OpperationSwitch");
 //error code
 }
 }//end OpperationSwitch
 #endregion
 #region ParserFunctions
 private void ParseEquation(char[] equation)
 {
 for (int i = 0; i < equation.Length; i++)
 {
 priorType = null;
 //Console.WriteLine("ParseEquation:= i: {0} ; Char: {1}", i, equation[i]);
 if (char.IsDigit(equation[i]) || equation[i] == '.')
 {
 //executes if # or .
 AddToBuffer(equation[i]);
 }
 else
 {
 //if buffer has data, create token, place in queue, clear buffer, and set prior type.
 if (bufferLoc != 0)
 {
 tokenQue.Enqueue(CreateToken(TokenType.Value, new string(buffer, 0, bufferLoc)));
 Array.Clear(buffer, 0, bufferLoc);
 bufferLoc = 0;
 priorType = TokenType.Value;
 }
 //handles operands
 OperationHandler(equation[i]);
 }//end else-if
 }//end while
 //creates a token of anything remaining in the buffer, and queues it
 if (bufferLoc != 0)
 {
 tokenQue.Enqueue(CreateToken(TokenType.Value, new string(buffer, 0, bufferLoc)));
 Array.Clear(buffer, 0, bufferLoc);
 bufferLoc = 0;
 }
 //moves anything remaining on the tokenStack to the que
 while (tokenStack.Count > 0)
 {
 Token temp = tokenStack.Pop();
 if (temp.VALUE == "(")
 {
 //will only run in case of unpaired ()
 Console.WriteLine("Error with '(' in ParseEquation");
 //error code
 }
 tokenQue.Enqueue(temp);
 }
 }//end ParseEquation
 private void OperationHandler(char opp)
 {
 if (opp == ')')
 {
 //runs if the opp is )
 //Console.WriteLine("Entering ')' section of OperationHandler");
 Token temp = null;
 bool found = false;
 //cycles until the most recent ( is found. If not found, error.
 while (tokenStack.Count != 0)
 {
 temp = tokenStack.Pop();
 if (temp.VALUE == "(")
 {
 found = true;
 return;
 }
 else
 {
 tokenQue.Enqueue(temp);
 }
 }
 if (!found)
 {
 Console.WriteLine("Error with '(' section of OperationHandler");
 //error code
 }
 }
 else
 {
 Token temp = CreateToken(TokenType.Operand, opp.ToString());
 //creates a multiplication incase of implicite multiplication. Ex: 5(4) => 5*4
 if (opp == '(' && priorType == TokenType.Value)
 {
 //Console.WriteLine("Creating a multiplication");
 tokenStack.Push(CreateToken(TokenType.Operand, "*"));
 }
 if (tokenStack.Count == 0)
 {
 //runs if stack is empty
 tokenStack.Push(temp);
 }
 else
 {
 OrderOfOpp(temp);
 }
 }
 }//end OperationHandler
 private void OrderOfOpp(Token t)
 {
 string tempVal = null;
 Dictionary<string, int> oppDic = new Dictionary<string, int>() {{"(",4 }, {"^",3},{"*",2},{"/",2},{"+",1},{"-",1}};
 bool done = false;
 while (!done || tokenStack.Count == 0)
 {
 tempVal = tokenStack.Peek().VALUE;
 if (tempVal == "(")
 {
 //if most recent push to stack was (, auto push t to stack
 tokenStack.Push(t);
 done = true;
 }
 else
 {
 if (oppDic[t.VALUE] >= oppDic[tempVal])
 {
 //if the t token has a higher precident then the tempVal (Obtained from peek), pushes t to que
 tokenStack.Push(t);
 done = true;
 }
 else
 {
 //if the tempVal has a higher precident then pop and enqueue
 tokenQue.Enqueue(tokenStack.Pop());
 }
 }
 if (tokenStack.Count == 0)
 {
 //if stack is empty, push to stack
 tokenStack.Push(t);
 done = true;
 }
 }//end while
 if (!done)
 {
 Console.WriteLine("Error in OrderOfOpp");
 //error code
 }
 }//end OrderOfOpp
 #endregion
 #region HelperFunctions
 private void AddToBuffer(char add)
 {
 buffer[bufferLoc] = add;
 bufferLoc++;
 }//end AddToBuffer
 private Token CreateToken(TokenType type, string value)
 {
 return new Token(type, value);
 }//end CreateToken
 #endregion
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked May 1, 2014 at 0:42
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

One thing, using a Dictionary<char, Func<double, double, double>> will simplify your translation from the parsed formula to the actual calculations:

public class Calculator
{
 public static readonly Dictionary<char, Func<double, double, double>> functions = new Dictionary<char, Func<double, double, double>>()
 {
 {'+',new Func<double,double,double>(Add)},
 {'-', new Func<double,double,double>(Subtract)},
 {'/',new Func<double,double,double>(Divide)},
 {'*',new Func<double,double,double>(Multiply)}
 };
 static double Add(double num1, double num2)
 {
 return num1 + num2;
 }
 static double Subtract(double num1, double num2)
 {
 return num1 - num2;
 }
 static double Multiply(double num1, double num2)
 {
 return num1 * num2;
 }
 static double Divide(double num1, double num2)
 {
 return num1 / num2;
 }
}

The calculation becomes a simple look up of the dictionary with the appropriate operator:

char operation = '+';
double num1 = 1;
double num2 = 2;
double newresult = Calculator.functions[operation](num1, num2);
answered May 1, 2014 at 21:01
\$\endgroup\$
1
  • \$\begingroup\$ I never thought of using a delegate.... I've only been coding for a month or so and never encountered/seen a place where one would be useful. \$\endgroup\$ Commented May 1, 2014 at 22:14

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.