148 lines
3 KiB
Elixir
148 lines
3 KiB
Elixir
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()
|