16 Jul 2013, 02:25
Dave_gnome_head_isolated_pragsmall

Dave Thomas (338 posts)

  • (Harder) Write a function that takes a single-quoted string of the form number [+-*/] number and returns the result of the calculation. The individual numbers do not have leading plus or minus signs.

    calculate('123 + 27') # => 150

A Possible Solution
defmodule Parse do

  def calculate(expression) do
    { n1, rest } = parse_number(expression)
    rest         = skip_spaces(rest)
    { op, rest } = parse_operator(rest)
    rest         = skip_spaces(rest)
    { n2, [] }   = parse_number(rest)
    op.(n1, n2)
  end

  defp parse_number(expression), do: _parse_number({ 0, expression })

  defp _parse_number({value, [ digit | rest ] }) when digit in ?0..?9 do
    _parse_number({ value*10 + digit - ?0, rest})
  end

  defp _parse_number(result), do: result


  defp skip_spaces([ ?  | rest ]), do: skip_spaces(rest)
  defp skip_spaces(rest),          do: rest

  defp parse_operator([ ?+ | rest ]), do: { &1+&2, rest }
  defp parse_operator([ ?- | rest ]), do: { &1-&2, rest }
  defp parse_operator([ ?* | rest ]), do: { &1*&2, rest }
  defp parse_operator([ ?/ | rest ]), do: { div(&1, &2), rest }

end

IO.inspect Parse.calculate('23+45')     #=> 68
IO.inspect Parse.calculate('34  - 56')  #=> -22
IO.inspect Parse.calculate('12 * 23')   #=> 276
IO.inspect Parse.calculate('123 / 8')   #=> 15

06 Sep 2013, 00:57
Generic-user-small

Arvind Dhiman (2 posts)

I ended up browsing elixir library to find how to trim spaces in a string and convert string to a number. I have following implementation.

I have regular expression twice in the function. How can I compile it so that I can reuse expression?


defmodule MyString do

    def eval(expression) do
            list = Regex.split(%r/\*|\+|\/|-/,expression)
            op = Regex.run(%r/\*|\+|\/|-/,expression)
            [n1,n2] = list
            _eval([_to_int(n1),_to_int(n2)], List.flatten(op))
    end
     
    defp _to_int(n), do: binary_to_integer(String.strip(String.from_char_list!(n)))
     
    defp _eval([n1, n2], '+'), do: n1 + n2
    defp _eval([n1, n2], '-'), do: n1 - n2
    defp _eval([n1, n2], '/'), do: div(n1, n2)
    defp _eval([n1, n2], '*'), do: n1 * n2

end 

07 Sep 2013, 11:03
Generic-user-small

Daniel Rose (1 post)

Here is mine. It basically uses Enum.chunk_by/2 to extract the operands and the operator, and then uses pattern matching to do the correct calculation.

defmodule Calculator do
  def calculate(operation) do
    { operand1, operator, operand2 } = extract_parts(operation)
    calculate(operator, operand1, operand2)
  end

  defp calculate('+', operand1, operand2), do: operand1 + operand2
  defp calculate('-', operand1, operand2), do: operand1 - operand2
  defp calculate('*', operand1, operand2), do: operand1 * operand2
  defp calculate('/', operand1, operand2), do: div(operand1, operand2)

  defp extract_parts(operation) do
    space?    = &( &1 == (? ) )
    operator? = &( &1 in '+-/*' )
    [operand1, operator, operand2] = operation |> Enum.reject(space?)
                                               |> Enum.chunks_by(operator?)
    { chars_to_integer(operand1), operator, chars_to_integer(operand2) }
  end

  defp chars_to_integer(characters) do
    characters |> to_string |> binary_to_integer
  end
end
30 Oct 2013, 21:50
Mac 128k logic board 96x96_pragsmall

Roger Turner (7 posts)


  def calculate(expression) do
    { left, rest } = Enum.split_while(expression, &(!(&1 in '+-*/')))
    [ op | right ] = rest
    { result, _ } = Code.eval_quoted { list_to_atom([op]), [], 
      [value(left), value(right)] }
    result
  end

  defp value(padded_digits), do:
    padded_digits |> to_string |> String.strip |> binary_to_integer

04 Jan 2014, 01:54
Generic-user-small

Eric Liaw (3 posts)

Since this chapter was shortly have list recursion I decided to try using Enum.reduce for extracting. Not really happy with how verbose _extract_helper ended up though…

	def calculate(string_list) do
		{num1, op, num2} = _extract_components(string_list)
		case op do
			?+ -> number(num1) + number(num2)
			?/ -> number(num1) / number(num2)
			?* -> number(num1) * number(num2)
			?- -> number(num1) - number(num2)
		end
	end

	defp _extract_components(string_list) do
		Enum.reduce(string_list, {nil, nil, nil}, fn(elem, acc) -> _extract_helper(elem, acc) end)
	end

	# Ignore spaces
	defp _extract_helper(? , acc), do: acc
	
	defp _extract_helper(elem, {nil, nil, _num2})
	when elem in '01234566789' do
		{[elem], nil, nil}
	end
	
	defp _extract_helper(elem, {num1, nil, _num2})
	when elem in '01234566789' do
		{num1 ++ [elem], nil, nil}
	end
	
	defp _extract_helper(elem, {num1, nil, _num2})
	when elem in '/+-*' do
		{num1, elem, nil}
	end
	
	defp _extract_helper(elem, {num1, op, nil})
	when elem in '01234566789' do
		{num1, op, [elem]}
	end
	
	defp _extract_helper(elem, {num1, op, num2})
	when elem in '01234566789' do
		{num1, op, num2 ++ [elem]}
	end
	
	defp number(characters) do
		characters |> to_string |> binary_to_integer
	end
12 Jan 2014, 04:40
Generic-user-small

Daniel Ashton (7 posts)

This would have been four lines shorter if I had found a way to dynamically reference Kernel.+/2 and friends.

  def calculate(str) do 
    {x, op, y} = _parse(str, {0, :op, 0})
    op.(x, y)
  end

  defp _parse([]     , acc      )                 , do: acc
  defp _parse([h | t], {a, b, c}) when h in ?0..?9, do: _parse(t, {a, b, c * 10 + h - ?0})
  defp _parse([h | t], {_, _, c}) when h in '+-*/', do: _parse(t, {c, _fn(h), 0})
  defp _parse([_ | t], acc      )                 , do: _parse(t, acc)

  defp _fn(?+), do: &Kernel.+/2
  defp _fn(?-), do: &Kernel.-/2
  defp _fn(?*), do: &Kernel.*/2
# defp _fn(?/), do: &Kernel.//2   # Nope, guess again
  defp _fn(?/), do: &div/2        # or &(&1 / &2) or ("#{div &1, &2} remainder #{rem &1, &2}")
14 Jan 2014, 01:22
Generic-user-small

Daniel Ashton (7 posts)

Thanks to José Valim answering my question on StackOverflow, here’s a version that calls the operator dynamically:

  def calculate(str) do 
    {x, op, y} = _parse(str, {0, :op, 0})
    apply :erlang, list_to_atom(op), [x, y]
  end

  defp _parse([]     , acc      )                 , do: acc
  defp _parse([h | t], {a, b, c}) when h in ?0..?9, do: _parse(t, {a, b, c * 10 + h - ?0})
  defp _parse([h | t], {_, _, c}) when h in '+-*/', do: _parse(t, {c, [h], 0})
  defp _parse([_ | t], acc      )                 , do: _parse(t, acc)
24 Jan 2014, 13:16
Generic-user-small

Dmitriy Ukhalov (1 post)

defmodule Parse do
	def calculate(str), do: _calculate(str, 0)

	defp _calculate([], value), do: value
	defp _calculate([?  | tail], value), do: _calculate(tail, value)
	defp _calculate([digit | tail], value) when digit in '0123456789', do: _calculate(tail, value * 10 + digit - ?0)
	defp _calculate([operator | tail], value) when operator in '+-*/', do: apply(:erlang, list_to_atom([operator]), [value, calculate(tail)])
end

IO.inspect Parse.calculate('23+45')     #=> 68
IO.inspect Parse.calculate('34  - 56')  #=> -22
IO.inspect Parse.calculate('12 * 23')   #=> 276
IO.inspect Parse.calculate('123 / 8')   #=> 15.375

# Also: 
IO.inspect Parse.calculate('123 + 40 / 8')   #=> 128.0
IO.inspect Parse.calculate('123 * 10 / 2')   #=> 615.0
  You must be logged in to comment