From 339fda2f909613ea843020dae7fd9e4d4c849e6e Mon Sep 17 00:00:00 2001 From: Jeremy Kaplan Date: Thu, 17 Dec 2020 23:21:21 -0800 Subject: [PATCH] Day 18 Part 2 --- 2020/day18/day18.ex | 133 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/2020/day18/day18.ex b/2020/day18/day18.ex index 7266754..e0d491d 100644 --- a/2020/day18/day18.ex +++ b/2020/day18/day18.ex @@ -112,6 +112,125 @@ defmodule NewMath do end end +defmodule NextMath 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 + + 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 + + # Grammar + # + # Expr = Product + # Atom = '(' Expr ')' | integer + # Sum = Atom ( '+' Atom )* + # Product = Sum ( '*' Sum )* + + def parse(tokens) do + {expr, []} = parse_expr(tokens) + expr + end + + defp parse_expr(tokens) do + parse_product(tokens) + end + + defp parse_product(tokens) do + {lexpr, remaining} = parse_sum(tokens) + parse_product_iter(remaining, lexpr) + end + + defp parse_product_iter(tokens, lexpr) do + case tokens do + [:times | rest] -> + {rexpr, leftover} = parse_sum(rest) + product = %Times{lexpr: lexpr, rexpr: rexpr} + parse_product_iter(leftover, product) + + _ -> + {lexpr, tokens} + end + end + + defp parse_sum(tokens) do + {lexpr, remaining} = parse_atom(tokens) + parse_sum_iter(remaining, lexpr) + end + + defp parse_sum_iter(tokens, lexpr) do + case tokens do + [:plus | rest] -> + {rexpr, leftover} = parse_atom(rest) + sum = %Plus{lexpr: lexpr, rexpr: rexpr} + parse_sum_iter(leftover, sum) + + _ -> + {lexpr, tokens} + end + end + + defp parse_atom([:lparen | tokens]) do + {expr, [:rparen | rest]} = parse_expr(tokens) + {expr, rest} + end + + defp parse_atom([{:integer, value} | tokens]) do + {%Integer{value: value}, tokens} + 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)) @@ -130,6 +249,7 @@ defmodule Day18 do defp test_input do """ + 1 + (2 * 3) + (4 * (5 + 6)) 2 * 3 + (4 * 5) 5 + (8 * 3 + 9 + 3 * 4 * 3) 5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4)) @@ -143,6 +263,19 @@ defmodule Day18 do |> Enum.map(&NewMath.evaluate/1) |> Enum.sum() end + + def part2 do + read_input() + |> String.split("\n", trim: true) + |> Enum.map(fn line -> + line + |> NextMath.tokenize() + |> NextMath.parse() + end) + |> Enum.map(&NextMath.evaluate/1) + |> Enum.sum() + end end Day18.part1() |> IO.inspect() +Day18.part2() |> IO.inspect()