Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

AoC 2022 Day 21

2022/day21.livemd

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