16 Jul 2013, 02:25
Dave_gnome_head_isolated_pragsmall

Dave Thomas (342 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</summary>

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

</details>

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
12 Nov 2014, 03:31
Generic-user-small

Elliot Finley (11 posts)

I have to admit I lost my patience on this one and just cheated :)

defmodule Calc do
  def eval(expression) do
    {result, _} = Code.eval_string(expression)
    result
  end
end
28 Mar 2015, 10:41
Maik_schmidt_avatar2_pragsmall

Maik Schmidt (116 posts)

Dave’s original solution no longer works with recent versions of Elixir. You’ll get the following output:

pi@elixirpi ~/learn_elixir $ elixir parse.exs
parse.exs:19: warning: found ? followed by codepoint 0x20 (space), please use \s instead
** (CompileError) parse.exs:22: unhandled &1 outside of a capture
    (stdlib) lists.erl:1352: :lists.mapfoldl/3
    (stdlib) lists.erl:1352: :lists.mapfoldl/3
    (elixir) src/elixir_clauses.erl:36: :elixir_clauses.clause/7

Here’s a version that works with the latest version of Elixir:

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([ ?\s | 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
05 May 2015, 14:48
Generic-user-small

Derick Thomas (3 posts)

This is my solution. Turned out to be much simpler than initially what I thought.


defmodule Parse do
  defp _calculate([], num1, ?+, num2) do: num1 + num2
  defp _calculate([], num1, ?-, num2) do: num1 - num2
  defp _calculate([], num1, ?*, num2) do: num1 * num2
  defp _calculate([], num1, ?/, num2) do: num1 / num2
  defp _calculate([head | tail], num1, ?=, 0) when head == 32, do: _calculate(tail, num1, ?=, 0)
  defp _calculate([head | tail], num1, ?=, 0) when head in [?+, ?-, ?*, ?/], do: _calculate(tail, num1, head, 0)
  defp _calculate([head | tail], num1, ?=, 0), do: _calculate(tail, (num1*10)+(head-?0), ?=, 0)
  defp _calculate([head | tail], num1, op, 0) when head == 32, do: _calculate(tail, num1, op, 0)
  defp _calculate([head | tail], num1, op, num2), do: _calculate(tail, num1, op, (num2*10)+(head-?0))

  def calculate(string) do
    _calculate(string, 0, ?=, 0)
  end
end

iex(90)> Parse.calculate('123000 + 27')            
123027
iex(91)> Parse.calculate('123000+27')  
123027
iex(92)> Parse.calculate('123000 +27')
123027
iex(93)> Parse.calculate('123000+ 27')



10 May 2015, 17:36
Generic-user-small

T. Kort (1 post)

Conveniently with strings:

defmodule Comp do

  def calc(charl) do
    [op1, op, op2] = to_string(charl) |> String.split
    ops = [op1, op2] |> Enum.map(&String.to_integer/1)
    fun = fn
      "+", [op1, op2] -> op1 + op2
      "-", [op1, op2] -> op1 - op2
      "*", [op1, op2] -> op1 * op2
      "/", [op1, op2] -> op1 / op2
    end
    fun.(op, ops)
  end
end
21 May 2015, 21:01
Dj-mangatar_pragsmall

Wirianto Djunaidi (5 posts)

I tried to solve this without using built in functions, I reused Dave’s example in the book for converting digit to integer.

defmodule MyString do

  def calculate(str) do
    {left, op, right} = _parse(str, '')
    _calculate(_number_digits(left, 0), _number_digits(right, 0), op)
  end

  defp _calculate(left, right, op) when op == ?+, do: left + right
  defp _calculate(left, right, op) when op == ?-, do: left - right
  defp _calculate(left, right, op) when op == ?*, do: left * right
  defp _calculate(left, right, op) when op == ?/, do: left / right

  defp _parse([], value), do: { value, '', ' ' }
  defp _parse([head | tail], value) when head in '+-*/' do
    { value, head, tail } 
  end
  defp _parse([head | tail], value) do
    _parse(tail, value ++ [head])
  end

  defp _number_digits([], value), do: value
  defp _number_digits([ digit | tail ], value)
  when digit in '0123456789' do
    _number_digits(tail, value * 10 + digit - ?0)
  end
  defp _number_digits([ digit | tail ], value)
  when digit in ' _' do
    _number_digits(tail, value)
  end

end
12 Jun 2015, 08:35
2011-11-23-square_pragsmall

Daniel Garcia (4 posts)

My solution is like T. Kort’s but a little shorter. With the problem it won’t work if the numbers are not separated by spaces.

defmodule MyString
  def parse(xs) do
    [num1, operator, num2] = to_string(xs) |> String.split

    apply Kernel, String.to_atom(operator), Enum.map([num1, num2], &String.to_integer/1)
  end
end
19 Jun 2015, 17:06
Generic-user-small

Sasa Ranisavljevic (1 post)

defmodule Strings do
  def calculate(exp), do: _calculate(exp, 0)
  defp _calculate([], value), do: value
  defp _calculate([? | t], value), do: _calculate(t, value)
  defp _calculate([?+ | t], value), do: value + _calculate(t, 0)
  defp _calculate([?- | t], value), do: value - _calculate(t, 0)
  defp _calculate([?* | t], value), do: value * _calculate(t, 0)
  defp _calculate([?/ | t], value), do: value / _calculate(t, 0)
  defp _calculate([digit | t], value) when digit in '0123456789' do
    _calculate(t, value*10 + digit - ?0)
  end
end

There is, however, this error:

warning: found ? followed by codepoint 0x20 (space), please use \s instead

  You must be logged in to comment