DEV Community

dev.to staff
dev.to staff

Posted on

Daily Challenge #10 - Calculator

We're back with another code challenge, this one comes from user obrok on Codewars:

Create a simple calculator that given a string of operators (+ - * and /) and numbers separated by spaces returns the value of that expression

Example:

Calculator().evaluate("2 / 2 + 3 * 4 - 6") # => 7

Remember about the order of operations! Multiplications and divisions have a higher priority and should be performed left-to-right. Additions and subtractions have a lower priority and should also be performed left-to-right.

Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Latest comments (25)

Collapse
 
celyes profile image
Ilyes Chouia

a bit late... this is the solution in PHP

function evaluate(string $x){
    return eval('return '.$x.';');
}
echo evaluate("2 / 2 + 3 * 4 - 6");
Collapse
 
margo1993 profile image
margo1993
package utils

import (
    "errors"
    "fmt"
    "math"
    "regexp"
    "strconv"
    "strings"
)

func evaluate(expression string) (float64, error) {
    supportedExpressions := []string{"/", "*", "+", "-"}

    expression = strings.Replace(expression, " ", "", len(expression) / 2)

    for _, e := range supportedExpressions {
        for moreExpressionsAvailable(expression, e) {
            firstExpression := findFirstExpression(expression, e)
            calculatedString, e := calculateString(firstExpression)

            if e != nil {
                return 0, e
            }

            expression = strings.Replace(expression, firstExpression, fmt.Sprintf("%0.2f", calculatedString), 1)
        }
    }
    result, e := strconv.ParseFloat(expression, 32)
    return math.Round(result * 100) / 100, e
}

func moreExpressionsAvailable(string string, e string) bool {
    r := regexp.MustCompile("\\" + e)
    return r.MatchString(string)
}

func findFirstExpression(expression string, operation string) string {
    r := regexp.MustCompile("[0-9.]+\\" + operation + "[0-9.]+")
    return r.FindString(expression)
}

func calculateString(expression string) (float64, error) {
    r := regexp.MustCompile("([0-9.]+)([/*+\\-])([0-9.]+)")

    submatches := r.FindStringSubmatch(expression)

    if len(submatches) < 3 {
        return 0, errors.New("not enought data")
    }

    num1, _ := strconv.ParseFloat(submatches[1], 32)
    numb2, _ := strconv.ParseFloat(submatches[3], 32)
    result, e := calculate(num1, numb2, submatches[2])
    return result, e
}

func calculate(num1 float64, num2 float64, operation string) (float64, error) {

    switch operation {
    case "*":
        return num1 * num2, nil
    case "/":
        return num1 / num2, nil
    case "+":
        return num1 + num2, nil
    case "-":
        return num1 - num2, nil
    }

    return 0, errors.New("Unsupported operator")
}
Collapse
 
wolverineks profile image
Kevin Sullivan • Edited
interface Number {
  type: "NUMBER" | string;
  value: number;
}

interface Operation {
  type: "OPERATION";
  value: "*" | "/" | "+" | "-";
}

const OPERATIONS = {
  "*": multiply,
  "/": divide,
  "+": add,
  "-": subtract
} as const;

const parse = (input: string): (Operation | Number)[] =>
  input
    .split(" ")
    .map(
      (input: string): Number | Operation =>
        input === "*"
          ? { type: "OPERATION", value: "*" }
          : input === "/"
          ? { type: "OPERATION", value: "/" }
          : input === "+"
          ? { type: "OPERATION", value: "+" }
          : input === "-"
          ? { type: "OPERATION", value: "-" }
          : { type: "NUMBER", value: Number(input) }
    );

export const combine = ({ value }: Operation, a: Number, b: Number) => ({
  type: "NUMBER",
  value: OPERATIONS[value](a.value, b.value)
});

export const doOperations = (operations: ("*" | "/" | "+" | "-")[]) => (
  input: (Operation | Number)[]
) => {
  let result = input;

  const firstOperationIndex = (list: (Operation | Number)[]) =>
    list.findIndex(
      ({ type, value }) =>
        type === "OPERATION" && operations.includes(value as any)
    );

  const operationsRemaining = (result: (Operation | Number)[]) =>
    ~firstOperationIndex(result);

  while (operationsRemaining(result)) {
    const operationIndex = firstOperationIndex(result);
    const operation = result[operationIndex] as Operation;

    const firstNumberIndex = operationIndex - 1;
    const firstNumber = result[firstNumberIndex] as Number;

    const secondNumberIndex = operationIndex + 1;
    const secondNumber = result[secondNumberIndex] as Number;

    result = result.reduce(
      (result: (Operation | Number)[], current: Operation | Number, index) => {
        return index === operationIndex
          ? [...result, combine(operation, firstNumber, secondNumber)]
          : [firstNumberIndex, secondNumberIndex].includes(index)
          ? result
          : [...result, current];
      },
      []
    );
  }

  return result;
};

export const calculate = pipe(
  parse,
  doOperations(["*", "/"]),
  doOperations(["+", "-"]),
  map(prop("value")),
  head
);
Collapse
 
kvharish profile image
K.V.Harish

My solution in js

calculator = () => {
  return {
    evaluate: (str) => {
      try{
        return eval(str)
      }
      catch(error) {
        return 'Invalid expression'
      }
    }
  } 
}

calculator().evaluate('2 / 2 + 3 * 4 - 6') // 7
calculator().evaluate('2 / 2 + ') // Invalid expression
Collapse
 
oscherler profile image
Olivier “Ölbaum” Scherler

I’m (still) learning Erlang. This is my solution with the given operators and integer numbers. I’m quite satisfied:

-module( calc ).
-export( [ calc/1 ] ).

-include_lib("eunit/include/eunit.hrl").

% expr := term [+|- term]*
% term := factor [*|/ factor]*
% factor := number
% number := -?digit+

skip_ws( [ $\  | Rest ] ) ->
    skip_ws (Rest );
skip_ws( [ $\t | Rest ] ) ->
    skip_ws (Rest );
skip_ws( Rest ) ->
    Rest.

parse_number( [ $- | S ] ) ->
    parse_number( S, undef, -1 );
parse_number( S ) ->
    parse_number( S, undef, 1 ).

parse_number( [ C | Rest ], Value, Sign ) when C >= $0, C =< $9 ->
    NewValue = case Value of
        undef -> 0;
        _ -> Value
    end * 10 + ( C - $0 ),
    parse_number( Rest, NewValue, Sign );
parse_number( Rest, Value, Sign ) ->
    case Value of
        undef -> { invalid, "" };
        _ -> { Sign * Value, Rest }
    end.

parse_factor( S ) ->
    parse_number( S ).

parse_term( S ) ->
    parse_term( S, 1, $* ).

parse_term( S, Value, Op ) ->
    { F1, R1 } = parse_factor( S ),

    NewValue = case Op of
        $* -> Value * F1;
        $/ -> Value / F1
    end,

    R2 = skip_ws( R1 ),
    case R2 of
        [ $* | R3 ] -> parse_term( skip_ws( R3 ), NewValue, $* );
        [ $/ | R3 ] -> parse_term( skip_ws( R3 ), NewValue, $/ );
        _ -> { NewValue, R2 }
    end.

parse_expr( S ) ->
    parse_expr( S, 0, $+ ).

parse_expr( S, Value, Op ) ->
    { F1, R1 } = parse_term( S ),

    NewValue = case Op of
        $+ -> Value + F1;
        $- -> Value - F1
    end,

    R2 = skip_ws( R1 ),
    case R2 of
        [ $+ | R3 ] -> parse_expr( skip_ws( R3 ), NewValue, $+ );
        [ $- | R3 ] -> parse_expr( skip_ws( R3 ), NewValue, $- );
        _ -> { NewValue, R2 }
    end.

calc( S ) ->
    { Result, [] } = parse_expr( skip_ws( S ) ),
    Result.

% TESTS

skip_ws_test_() -> [
    ?_assertEqual( "", skip_ws("") ),
    ?_assertEqual( "", skip_ws(" ") ),
    ?_assertEqual( "", skip_ws("  ") ),
    ?_assertEqual( "", skip_ws( [ $\t ] ) ),
    ?_assertEqual( "ASDF", skip_ws("  ASDF") ),
    ?_assertEqual( "AS DF ", skip_ws( [ $\ , $\t, $\ , $\ , $A, $S, $\ , $D, $F, $\  ] ) )
].

parse_number_test_() -> [
    ?_assertEqual( { 0, "" }, parse_number("0") ),
    ?_assertEqual( { 2, "" }, parse_number("2") ),
    ?_assertEqual( { 42, "" }, parse_number("42") ),
    ?_assertEqual( { -1, "" }, parse_number("-1") ),
    ?_assertEqual( { -145, "" }, parse_number("-145") ),
    ?_assertEqual( { -145, " some stuff" }, parse_number("-145 some stuff") ),
    ?_assertEqual( { invalid, "" }, parse_number("asdf") ),
    ?_assertEqual( { invalid, "" }, parse_number("-") ),
    ?_assertEqual( { invalid, "" }, parse_number("-stuff") ),
    ?_assertEqual( { invalid, "" }, parse_number("- stuff") )
].

parse_term_test_() -> [
    ?_assertEqual( { 6, "" }, parse_term("3 * 2") ),
    ?_assertEqual( { -6, "" }, parse_term("-3 * 2") ),
    ?_assertEqual( { -6, "" }, parse_term("3 * -2") ),
    ?_assertEqual( { 6, "" }, parse_term("-3 * -2") )
].

parse_expr_test_() -> [
    ?_assertEqual( { 5, "" }, parse_expr("3 + 2") ),
    ?_assertEqual( { 1, "" }, parse_expr("3 - 2") ),
    ?_assertEqual( { 1, "" }, parse_expr("3 + -2") ),
    ?_assertEqual( { -1, "" }, parse_expr("-3 - -2") )
].

calc_test_() -> [
    ?_assertEqual( 0, calc("0") ),
    ?_assertEqual( 2, calc("2") ),
    ?_assertEqual( -3, calc("-3") ),
    ?_assertEqual( 128, calc("128  ") ),
    ?_assertEqual( 128, calc(" 128  ") ),
    ?_assertEqual( 14, calc("2 + 3 * 4") ),
    ?_assertEqual( 9.0, calc("6 / 2 * 3") ),
    ?_assertEqual( 1, calc("-1 - -2") ),
    ?_assertEqual( 11, calc("3 * -2 + 17") )
].

To run it:

% erl
1> c(calc).
{ok,calc}
2> calc:test().
  All 33 tests passed.
ok
3> calc:calc("2 + 3 * 4").
14
4> calc:calc("8 / 2 * 4").
16.0

Then I extended it with:

  • floating point numbers;
  • exponent notation (125e-2 = 1.25);
  • powers, with ^;
  • parentheses.

You can find it in this Gist, and try it:

% erl
1> c(calc).
{ok,calc}
2> calc:test().
  All 57 tests passed.
ok
3> calc:calc("1.5 * 2").
3.0
4> calc:calc("12e4 * 2e-3").
240.0
5> calc:calc("3 ^ 4").
81.0
6> calc:calc("2 ^ 0.5").
1.4142135623730951
7> calc:calc("2 + ( 3 * 4 )").
14.0
8> calc:calc("8 / 2 * (2 + 2)").
16.0 % OBVIOUSLY!
9> calc:calc("3^(1/2)").
1.7320508075688772

parse_number got a bit out of hand, but I find the rest quite elegant. I like Erlang.

Collapse
 
oscherler profile image
Olivier “Ölbaum” Scherler

I remember doing this in C in college. It handled Real numbers (even Complex at one point, I believe), supported variables, could graph a function of x and even calculate the derivative. Much fun.

Collapse
 
coreyja profile image
Corey Alexander

I missed this one yesterday! I'll get it done sometime this week though!

Collapse
 
deciduously profile image
Ben Lovy • Edited

Haskell:

import Data.Char (isDigit)

data Token = Num Int | Add | Sub | Mul | Div deriving (Eq, Show)

input :: String
input = "2 / 2 + 3 * 4 - 6"

tokenize :: String -> [String]
tokenize s = filter (/= " ") $ map (:[]) s

parse :: [String] -> [Token]
parse [] = []
parse (t:ts)
    | isDigit $ t !! 0 = Num (read t) : parse ts
    | t == "*" = Mul : parse ts
    | t == "/" = Div : parse ts
    | t == "+" = Add : parse ts
    | t == "-" = Sub : parse ts
    | otherwise = parse ts

division :: [Token] -> [Token]
division [] = []
division [a] = [a]
division (t:ts) =
    case t of
        Num x ->
            case head ts of
                Div ->
                    let
                        (Num y) = head $ tail ts
                    in
                        Num (quot x y) : division (tail $ tail ts)
                _ -> t : division ts
        _ -> t : division ts

multiplication :: [Token] -> [Token]
multiplication [] = []
multiplication [a] = [a]
multiplication (t:ts) =
    case t of
        Num x ->
            case head ts of
                Mul ->
                    let
                        (Num y) = head $ tail ts
                    in
                        Num (x * y) : multiplication (tail $ tail ts)
                _ -> t : multiplication ts
        _ -> t : multiplication ts

addition :: [Token] -> [Token]
addition [] = []
addition [a] = [a]
addition (t:ts) =
    case t of
        Num x ->
            case head ts of
                Add ->
                    let
                        (Num y) = head $ tail ts
                    in
                        Num (x + y) : addition (tail $ tail ts)
                _ -> t : addition ts
        _ -> t : addition ts

subtraction :: [Token] -> [Token]
subtraction [] = []
subtraction [a] = [a]
subtraction (t:ts) =
    case t of
        Num x ->
            case head ts of
                Sub ->
                    let
                        (Num y) = head $ tail ts
                    in
                        Num (x - y) : subtraction (tail $ tail ts)
                _ -> t : subtraction ts
        _ -> t : subtraction ts

evalStr :: String -> Int
evalStr s =
    let
        [(Num x)] = subtraction $ addition $ multiplication $ division $ parse $ tokenize s
    in
        x

Would anyone be able to help me abstract out that operation pattern? I'm a little stumped on how to de-duplicate this code, even though Haskell is great at that.

Collapse
 
alvaromontoro profile image
Alvaro Montoro • Edited

JavaScript

const calculator = operation => {

  // verify that the string with the operation has the right format:
  //   - a number
  //   - optionally followed by 0 or more:
  //       - a space
  //       - an operator (+-*/)
  //       - a space
  //       - another number
  if (!operation.match(/^\d+( [\+\-\/\*] \d+)*$/)) return null;

  // easy solution: now that we know the string has the format that we expect, 
  // return (eval(operation));
  // ...but someone did it already, so let's go the long way :P


  // break the string by spaces
  let ops = operation.split(" ");
  let opsSimple = [];

  // multiplication and division take priority
  // we create a new array that will only have numbers and + or -
  for (x = 0; x < ops.length; x++) {
    if (ops[x] === '*') {
      const val = opsSimple.pop();
      opsSimple.push(val * ops[x+1]);
      x++;
    } else if (ops[x] === '/') {
      // do not allow division by zero!
      if (ops[x+1] === "0") return "Error! Division by zero!";
      const val = opsSimple.pop();
      opsSimple.push(val / ops[x+1]);
      x++;
    } else {
      opsSimple.push(ops[x]);
    }
  }

  // calculate the addtiions and substractions sequentially
  let result = parseInt(opsSimple[0]);
  for (x = 1; x < opsSimple.length; x = x + 2) {
    if (opsSimple[x] === '+') {
      result += opsSimple[x+1];
    } else {
      result -= opsSimple[x+1];
    }
  }

  return result;
}

Live demo on CodePen

Collapse
 
alvaromontoro profile image
Alvaro Montoro

10 out of 10 challenges! :)

Although I was just able to make 2 using CSS only :-/

Collapse
 
coreyja profile image
Corey Alexander

I'm gonna try to keep doing em each day in July if I can, you game lol?

I'm currently a day behind you since I didn't get this one done yesterday

Thread Thread
 
alvaromontoro profile image
Alvaro Montoro

Let's do it!

Collapse
 
ganderzz profile image
Dylan Paulus • Edited

Nim.

from strutils import split, parseFloat
from sequtils import foldl, map
import re

type CalcKind = enum
  vkOperator
  vkValue

type
  CalcItem = ref object
    case Kind: CalcKind
    of vkOperator: Operator: string
    of vkValue: Value: float

proc toCalcItem(item: string): CalcItem =
  if item.match(re"\d+"): 
    return CalcItem(Kind: CalcKind.vkValue, Value: parseFloat(item))

  return CalcItem(Kind: CalcKind.vkOperator, Operator: item)

proc calculate(input: string): float =
  var stack: seq[CalcItem] = input.split(" ").map(toCalcItem)
  var backStack: seq[CalcItem]

  while len(stack) > 0:
    let item = stack.pop()

    if item.Kind == CalcKind.vkOperator:
      if item.Operator == "*":
        let curr = backStack.pop()
        let other = stack.pop()
        backStack.add(CalcItem(Kind: CalcKind.vkValue, Value: other.Value * curr.Value))
      elif item.Operator == "/":
        let curr = backStack.pop()
        let other = stack.pop()
        backStack.add(CalcItem(Kind: CalcKind.vkValue, Value: other.Value / curr.Value))
      else:
        backStack.add(item)
    else:
      backStack.add(item)

  while len(backStack) > 2:
    let curr = backStack.pop()
    let operator = backStack.pop()
    let other = backStack.pop()

    if operator.Operator == "+":
      backStack.add(CalcItem(Kind: CalcKind.vkValue, Value: curr.Value + other.Value))
    else:
      backStack.add(CalcItem(Kind: CalcKind.vkValue, Value: curr.Value - curr.Value))

  return foldl(backStack, a + b.Value, 0.0)


when isMainModule:
  echo $calculate("2 / 2 + 3 * 4 - 6") #7
Collapse
 
ganderzz profile image
Dylan Paulus • Edited

Did some cleanup using types. No more string->float->string conversions. Adding old solution below!

from strutils import split, parseFloat
from sequtils import foldl

proc calculate(input: string): float =
  var stack: seq[string] = input.split(" ")
  var backStack: seq[string]

  while len(stack) > 0:
    let item = stack.pop()

    if item == "*":
      let curr = parseFloat(backStack.pop())
      let other = parseFloat(stack.pop())
      backStack.add($(other * curr))
    elif item == "/":
      let curr = parseFloat(backStack.pop())
      let other = parseFloat(stack.pop())
      backStack.add($(other / curr))
    else:
      backStack.add(item)

  while len(backStack) > 2:
    let item = parseFloat(backStack.pop())
    let operator = backStack.pop()
    let other = parseFloat(backStack.pop())

    if operator == "+":
      backStack.add($(item + other))
    else:
      backStack.add($(item - other))

  return foldl(backStack, a + parseFloat(b), 0.0)


when isMainModule:
  echo $calculate("2 / 2 + 3 * 4 - 6") #7