Created
April 30, 2021 22:12
-
-
Save rogerleite/20173fa3bf982c5d5c1f97b1b7a99525 to your computer and use it in GitHub Desktop.
Revisions
-
rogerleite created this gist
Apr 30, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,159 @@ # Arithmetic expressions in plain English ## Interpret and evaluate arithmetic expressions written in plain English Example: "one plus two times four" * numbers are [zero-ten] * numbers can be negative, for example: "negative five" * "plus" and "times" are the only supported operations natural order of operations apply, multiply before add. * "negative" is optional string before the number and is not an operation "one minus two" is expressed as "one plus negative two" You are not allowed to use eval function that is built in the language macros or compiler internals external interpreter. ```elixir # Solution based on: https://panda.ime.usp.br/pythonds/static/pythonds_pt/02-EDBasicos/InfixPrefixandPostfixExpressions.html defmodule Plain do @operators ["plus", "times"] @operands [ "negative", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" ] @numbers %{ "one" => 1, "two" => 2, "three" => 3, "four" => 4, "five" => 5, "six" => 6, "seven" => 7, "eight" => 8, "nine" => 9, "ten" => 10 } @precedence %{"times" => 3, "plus" => 2} defp token_type(token) do cond do token in @operators -> :operator token in @operands -> :operand true -> :unknown end end defp add_operator_with_precedence(new_operator, operators) do [new_operator | operators] |> Enum.map(&%{operator: &1, precedence: @precedence[&1]}) |> Enum.sort_by(& &1.precedence, :desc) |> Enum.map(& &1.operator) end defp to_numbers(reverse_operands) do {values, _} = Enum.flat_map_reduce(reverse_operands, 1, fn operand, multiplier -> value = @numbers[operand] cond do is_nil(value) -> {[nil], -1} multiplier < 1 -> {[value * multiplier], 1} true -> {[value], 1} end end) values |> Enum.reject(&is_nil/1) end defp build_postfix(tokens) do [operands, operators] = Enum.reduce(tokens, [[], []], fn token, [operands, operators] -> case token_type(token) do :operand -> [[token | operands], operators] :operator -> [operands, add_operator_with_precedence(token, operators)] :unknown -> raise "Eval is aborted. Unknown token '#{token}' found." end end) Enum.concat(to_numbers(operands), operators) end defp split_by_operator(postfix) do Enum.reduce(postfix, %{op: nil, left: [], right: []}, fn token, acc -> case acc do %{op: nil, left: left} -> if token in @operators do Map.put(acc, :op, token) else Map.put(acc, :left, [token | left]) end %{right: right} -> Map.put(acc, :right, [token | right]) end end) end defp eval_postfix(postfix) do %{op: operator, left: left, right: right} = split_by_operator(postfix) [token1 | remain_left1] = left [token2 | remain_left2] = remain_left1 result = case operator do "times" -> token1 * token2 "plus" -> token1 + token2 end remain_postfix = remain_left2 |> Enum.concat([result]) |> Enum.concat(right) if length(remain_postfix) > 1 do eval_postfix(remain_postfix) else result end end def evaluate(expression, options \\ []) do inspect_option = Keyword.get(options, :inspect, false) postfix = String.split(expression) |> build_postfix result = postfix |> eval_postfix if inspect_option, do: IO.puts("#{expression}:\n postfix: #{inspect(postfix)}\n result: #{result}") result end end Plain.evaluate("one plus two times four") |> IO.puts() Plain.evaluate("one plus negative two") |> IO.puts() Plain.evaluate("two times ten") |> IO.puts() # Plain.evaluate("five times negative ten plus two", inspect: true) |> IO.puts() Plain.evaluate("five times negative ten plus two") |> IO.puts() try do Plain.evaluate("one thousand plus two") rescue ex -> IO.puts("one thousand plus two:\n" <> ex.message) end ```