Simple calculator

Within a interview they requested me to do some exercises at home and here is the question that they posed.

“Create a simple stack-based calculator. This should be capable of performing addition, subtraction, multiplication and division. Example input would be: “2*(3+1)”. The result should be expressed as a numeric value, so the example should return 8. Usage of the eval() function, although amusing, is not allowed! This test is designed to show general programming skills, rather than an understanding of the programming platform; however the solution should be demonstrated using php as the language.”

So I decided to post on this site as well the design and program that I did come up with.

Since in maths you have to equate the ( .. ) first and then * , / , + , – so to start with I created a function that will pull out the brackets from the string input

    /* need to find the brackets and then equate the value and build outwards */
    public function equate()
    {
       // this is the string that was inputted when the class was created ( $this->calcstr;)
	$theStr = $this->calcstr;
 
	$bracketI = strrpos($theStr, "(");
	// there is a bracket
	while ($bracketI >=0)
	{
	  // find a bracket to match
	  $bracketEnd = strpos($theStr, ")", $bracketI);
	  if ($bracketEnd > 0)
	  {
	    // match then get the internal bracket value
	    $EqualMiddle = substr($theStr, $bracketI+1, ($bracketEnd-$bracketI)-1);
	    $calMiddle = $this->calculateTheString($EqualMiddle);
	    // rebuild the string without the bracket but with the value
	    $theStr = substr($theStr,0, $bracketI) . $calMiddle. substr($theStr, $bracketEnd+1);
	  }
	  else
	    throw new Exception("Bracket miss matched");
 
	  $bracketI = strrpos($theStr, "(");
	  if ($bracketI == false && $theStr[0] != "(") break;
	}
	return $this->calculateTheString($theStr);
    }

So what happens is that it will find the last occurrence of a bracket “(” and then find the corresponding “)” for that bracket, error is not found. Once found, it will then send to a function called “calculateTheString” that will actually calculate the value.

The calculateTheString function

 private function calculateTheString($theStr)
    {
	// look for * / + - in that order and then get the number each side to do the math
	$mathStr = array("*", "/", "+", "-");
	foreach ($mathStr as $maths)
	{
	  do 
	  {
	    // find each occurence of the math func
	    $mathI = strpos($theStr, $maths);
	    if ($mathI > 0)
	    {
	      // find the numeric values before and after the math func
	      try {
		$valueBefore = $this->getNumbericValue(substr($theStr, 0,$mathI), 0);
		$valueAfter =  $this->getNumbericValue(substr($theStr, $mathI+1), 1);
	      } catch (Exception $ex)
	      {
		echo "Error : $ex (String = $theStr)";
	      }
	      // do the math func
	      switch($maths)
	      {
		case "*" : $newV = $valueBefore * $valueAfter; break;
		case "/" : $newV = $valueBefore / $valueAfter; break;
		case "+" : $newV =$valueBefore + $valueAfter; break;
		case "-" : $newV =$valueBefore - $valueAfter;; break;
		default : $newV =0;
	      }
	      // rebuild string without the math func that is done
	      $theStr = substr($theStr,0,($mathI-strlen($valueBefore))) . ($newV) . substr($theStr,($mathI+strlen($valueAfter)+1));
	    }
	  } while ($mathI >0);
	}
	return $theStr;
    }

will loop through the math functions (*,/,+,-) in order of calculation, if it finds one of them it will equate what the value of that block of math is e.g.

4*2+1

it would equate the 4*2 and then rebuild the string to have the result within the next loop e.g.

8+1

so that when it gets to the + math function it will add 8 to 1.

To get the number values before and after the math functions (*,/,+,-) I created a function to pull out the values called “getNumbericValue”.. since the values will either be before or after the e.g. going backwards or forwards in the string, then you will need to either go in that direction. Once you have got the area within the string of the numeric values, then use the intval to convert into a integer value. The is_numeric makes sure that there is still a numeric value present.

/* $theStr = string to search, leftOrRight means going left or right in the string, 0 = left, 1=right*/
    private function getNumbericValue($theStr, $leftOrRight=1)
    {
	if (strlen($theStr) == 0) throw new Exception("No number value");
	if ($leftOrRight ==1) { $pos =0; 
	  $start = 0;
	}else 
	{
	  $pos = strlen($theStr)-1;
	  $start = strlen($theStr) -1;
	}
 	while (true)
	{
	  if (is_numeric($theStr[$pos]))
	  {
	    if ($leftOrRight == 0) $pos--; else $pos++;
	  }
	    else break;
	}
	// if just the number at the start
	if ($pos == -1) return intval($theStr);
	// if the start is greater than the end point, change over
	if ($start > $pos) { $tmp = $pos; $pos = $start; $start = $tmp;}
	return intval(substr($theStr,$start, $pos));
    }

To build all that together with

<?php
  class calc 
  {
    private $calcstr = "";
 
    function __construct($strIn)
    {
      $this->calcstr = $strIn;
    }
 
    public function setStr($strIn = "")
    {
      $this->calcstr = $strIn;
    }
 
    /* $theStr = string to search, leftOrRight means going left or right in the string, 0 = left, 1=right*/
    private function getNumbericValue($theStr, $leftOrRight=1)
    {
	if (strlen($theStr) == 0) throw new Exception("No number value");
	if ($leftOrRight ==1) { $pos =0; 
	  $start = 0;
	}else 
	{
	  $pos = strlen($theStr)-1;
	  $start = strlen($theStr) -1;
	}
 	while (true)
	{
	  if (is_numeric($theStr[$pos]))
	  {
	    if ($leftOrRight == 0) $pos--; else $pos++;
	  }
	    else break;
	}
	// if just the number at the start
	if ($pos == -1) return intval($theStr);
	// if the start is greater than the end point, change over
	if ($start > $pos) { $tmp = $pos; $pos = $start; $start = $tmp;}
	return intval(substr($theStr,$start, $pos));
    }
 
    private function calculateTheString($theStr)
    {
	// look for * / + - in that order and then get the number each side to do the math
	$mathStr = array("*", "/", "+", "-");
	foreach ($mathStr as $maths)
	{
	  do 
	  {
	    // find each occurence of the math func
	    $mathI = strpos($theStr, $maths);
	    if ($mathI > 0)
	    {
	      // find the numeric values before and after the math func
	      try {
		$valueBefore = $this->getNumbericValue(substr($theStr, 0,$mathI), 0);
		$valueAfter =  $this->getNumbericValue(substr($theStr, $mathI+1), 1);
	      } catch (Exception $ex)
	      {
		echo "Error : $ex (String = $theStr)";
	      }
	      // do the math func
	      switch($maths)
	      {
		case "*" : $newV = $valueBefore * $valueAfter; break;
		case "/" : $newV = $valueBefore / $valueAfter; break;
		case "+" : $newV =$valueBefore + $valueAfter; break;
		case "-" : $newV =$valueBefore - $valueAfter;; break;
		default : $newV =0;
	      }
	      // rebuild string without the math func that is done
	      $theStr = substr($theStr,0,($mathI-strlen($valueBefore))) . ($newV) . substr($theStr,($mathI+strlen($valueAfter)+1));
	    }
	  } while ($mathI >0);
	}
	return $theStr;
    }
 
    /* need to find the brackets and then equate the value and build outwards */
    public function equate()
    {
	$theStr = $this->calcstr;
 
	$bracketI = strrpos($theStr, "(");
	// there is a bracket
	while ($bracketI >=0)
	{
	  // find a bracket to match
	  $bracketEnd = strpos($theStr, ")", $bracketI);
	  if ($bracketEnd > 0)
	  {
	    // match then get the internal bracket value
	    $EqualMiddle = substr($theStr, $bracketI+1, ($bracketEnd-$bracketI)-1);
	    $calMiddle = $this->calculateTheString($EqualMiddle);
	    // rebuild the string without the bracket but with the value
	    $theStr = substr($theStr,0, $bracketI) . $calMiddle. substr($theStr, $bracketEnd+1);
	  }
	  else
	    throw new Exception("Bracket miss matched");
 
	  $bracketI = strrpos($theStr, "(");
	  if ($bracketI == false && $theStr[0] != "(") break;
	}
	return $this->calculateTheString($theStr);
    }
 
  }
 
  $theequal = "2*(3+1)";
  $thecalc = new calc($theequal);
  try {
    echo $thecalc->equate();
  } catch (Exception $err)
  {	
    echo $err;
  }
?>

and the output would be

8

I found it a interesting exercise 🙂