AoC 2022 Day 21
Mix.install([
{:kino, "~> 0.7.0"},
{:kino_vega_lite, "~> 0.1.6"},
{:nimble_parsec, "~> 1.2"}
])
Input
input_field = Kino.Input.textarea("Puzzle input")
input = Kino.Input.read(input_field)
Part 1
defmodule Parser do
import NimbleParsec
name = ascii_string([?a..?z], 4) |> tag(:name)
operator =
choice([
string("+") |> tag(:add),
string("-") |> tag(:subtract),
string("*") |> tag(:multiply),
string("/") |> tag(:divide)
])
|> tag(:op)
constant = integer(min: 1) |> tag(:constant)
job =
name
|> ignore(string(" "))
|> concat(operator)
|> ignore(string(" "))
|> concat(name)
|> tag(:job)
monkey =
name
|> ignore(string(": "))
|> choice([constant, job])
|> ignore(optional(string("\n")))
|> tag(:monkey)
defparsec(:parse, repeat(monkey))
end
defmodule Monkey do
def add_to_graph(graph, {:monkey, [name: [name], constant: [num]]}) do
create_vertex(graph, name, {:constant, num})
end
def add_to_graph(graph, {:monkey, [name: [name], job: job]}) do
[name: [left], op: [{op, _}], name: [right]] = job
create_vertex(graph, left)
create_vertex(graph, right)
create_vertex(graph, name, {:operation, op})
:digraph.add_edge(graph, name, left, :left)
:digraph.add_edge(graph, name, right, :right)
end
def create_vertex(graph, vertex, label \\ []) do
case :digraph.vertex(graph, vertex) do
false -> :digraph.add_vertex(graph, vertex, label)
{^vertex, []} -> :digraph.add_vertex(graph, vertex, label)
_ -> :ok
end
end
def calculate_value(graph, vertex) do
case :digraph.vertex(graph, vertex) do
{^vertex, {:constant, n}} ->
n
{^vertex, {:operation, op}} ->
calculate_operation(graph, vertex, op)
end
end
def calculate_operation(graph, vertex, op) do
%{left: {_, ^vertex, left, :left}, right: {_, ^vertex, right, :right}} =
:digraph.out_edges(graph, vertex)
|> Enum.map(fn e -> :digraph.edge(graph, e) end)
|> Enum.map(&{elem(&1, 3), &1})
|> Enum.into(%{})
left_val = calculate_value(graph, left)
right_val = calculate_value(graph, right)
apply_op(op, left_val, right_val)
end
def apply_op(:add, left, right), do: left + right
def apply_op(:subtract, left, right), do: left - right
def apply_op(:multiply, left, right), do: left * right
def apply_op(:divide, left, right), do: Integer.floor_div(left, right)
end
{:ok, parsed, "", _, _, _} = Parser.parse(input)
graph = :digraph.new()
Enum.each(parsed, &Monkey.add_to_graph(graph, &1))
Monkey.calculate_value(graph, "root")
Part 2