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
    }
}
\$\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);
\$\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\$
    – JRLambert
    May 1, 2014 at 22:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.