From beba85f7ef48ba4d8d1264d0316d8483de7f085a Mon Sep 17 00:00:00 2001 From: Jeremy Kaplan Date: Thu, 17 Dec 2020 22:03:59 -0800 Subject: [PATCH] Day 18 Part 1 --- 2020/day18/day18.ex | 148 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 2020/day18/day18.ex diff --git a/2020/day18/day18.ex b/2020/day18/day18.ex new file mode 100644 index 0000000..7266754 --- /dev/null +++ b/2020/day18/day18.ex @@ -0,0 +1,148 @@ +defmodule NewMath do + defmodule Integer do + @enforce_keys [:value] + defstruct [:value] + end + + defmodule Plus do + @enforce_keys [:lexpr, :rexpr] + defstruct [:lexpr, :rexpr] + end + + defmodule Times do + @enforce_keys [:lexpr, :rexpr] + defstruct [:lexpr, :rexpr] + end + + defmodule Group do + @enforce_keys [:expr] + defstruct [:expr] + end + + defmodule Expr do + @enforce_keys [:expr] + defstruct [:expr] + end + + def tokenize(text) do + scan(String.codepoints(text), []) + end + + defp scan([], tokens) do + Enum.reverse(tokens) + end + + defp scan(text = [first | rest], tokens) do + case first do + "(" -> + scan(rest, [:lparen | tokens]) + + ")" -> + scan(rest, [:rparen | tokens]) + + "*" -> + scan(rest, [:times | tokens]) + + "+" -> + scan(rest, [:plus | tokens]) + + " " -> + scan(rest, tokens) + + _ -> + {digits, extra} = Enum.split_while(text, fn char -> String.match?(char, ~r/\d/) end) + scan(extra, [{:integer, String.to_integer(Enum.join(digits))} | tokens]) + end + end + + def parse(tokens) do + [%Expr{expr: expr}] = parse_iter(tokens, []) + expr + end + + defp parse_iter([], stack) do + stack + end + + defp parse_iter(tokens, stack) do + {rest, new_stack} = shift(tokens, stack) + reduced = reduce(new_stack) + parse_iter(rest, reduced) + end + + defp shift([token | rest], stack) do + gram = + case token do + {:integer, value} -> %Expr{expr: %Integer{value: value}} + _ -> token + end + + {rest, [gram | stack]} + end + + defp reduce(stack) do + case stack do + [:rparen, %Expr{expr: expr}, :lparen | rest] -> + new_expr = %Expr{expr: expr} + reduce([new_expr | rest]) + + [%Expr{expr: rexpr}, :plus, %Expr{expr: lexpr} | rest] -> + new_expr = %Expr{expr: %Plus{lexpr: lexpr, rexpr: rexpr}} + reduce([new_expr | rest]) + + [%Expr{expr: rexpr}, :times, %Expr{expr: lexpr} | rest] -> + new_expr = %Expr{expr: %Times{lexpr: lexpr, rexpr: rexpr}} + reduce([new_expr | rest]) + + _ -> + stack + end + end + + def evaluate(%Plus{lexpr: lexpr, rexpr: rexpr}) do + evaluate(lexpr) + evaluate(rexpr) + end + + def evaluate(%Times{lexpr: lexpr, rexpr: rexpr}) do + evaluate(lexpr) * evaluate(rexpr) + end + + def evaluate(%Integer{value: value}) do + value + end +end + +defmodule Day18 do + defp read_input do + Path.expand('input', Path.dirname(__ENV__.file)) + |> File.read!() + end + + defp parse_expressions(text) do + String.split(text, "\n", trim: true) + |> Enum.map(&parse_expression/1) + end + + defp parse_expression(line) do + NewMath.tokenize(line) + |> NewMath.parse() + end + + defp test_input do + """ + 2 * 3 + (4 * 5) + 5 + (8 * 3 + 9 + 3 * 4 * 3) + 5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4)) + ((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2 + """ + end + + def part1 do + read_input() + |> parse_expressions() + |> Enum.map(&NewMath.evaluate/1) + |> Enum.sum() + end +end + +Day18.part1() |> IO.inspect()