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

Day Twenty One

day21.livemd

Day Twenty One

Mix.install([
  {:kino, "~> 0.7.0"}
])

Section

input = Kino.Input.textarea("Input")
defmodule DayTwentyOne do
  def solve1(input) do
    monkeys =
      input
      |> String.split("\n", trim: true)
      |> parse()

    {v, _} = find_monkey("root", monkeys)
    v
  end

  def solve2(input) do
    monkeys =
      input
      |> String.split("\n", trim: true)
      |> parse()

    solve_monkey("root", monkeys)
  end

  defp solve_monkey("root", monkeys) do
    {:op, _, ma, mb} = monkeys["root"]
    monkeys = Map.put(monkeys, "humn", {:str, "humn"})
    {va, monkeys} = find_monkey(ma, monkeys)
    {{:done, v}, _} = find_monkey(mb, monkeys)

    reduce(va, v)
  end

  defp reduce({:str, {"+", {:done, v}, vb}}, right) do
    reduce(vb, right - v)
  end

  defp reduce({:str, {"+", vb, {:done, v}}}, right) do
    reduce(vb, right - v)
  end

  defp reduce({:str, {"-", {:done, v}, vb}}, right) do
    reduce(vb, v - right)
  end

  defp reduce({:str, {"-", vb, {:done, v}}}, right) do
    reduce(vb, right + v)
  end

  defp reduce({:str, {"*", {:done, v}, vb}}, right) do
    reduce(vb, div(right, v))
  end

  defp reduce({:str, {"*", vb, {:done, v}}}, right) do
    reduce(vb, div(right, v))
  end

  defp reduce({:str, {"/", {:done, v}, vb}}, right) do
    reduce(vb, div(v, right))
  end

  defp reduce({:str, {"/", vb, {:done, v}}}, right) do
    reduce(vb, right * v)
  end

  defp reduce(a, b) do
    {a, b}
  end

  defp find_monkey(m, monkeys) do
    case monkeys[m] do
      {:str, v} ->
        {{:str, v}, monkeys}

      {:done, v} ->
        {{:done, v}, monkeys}

      {:op, op, ma, mb} ->
        {va, monkeys} = find_monkey(ma, monkeys)
        {vb, monkeys} = find_monkey(mb, monkeys)
        v = apply_op(op, va, vb)
        monkeys = Map.put(monkeys, m, v)
        {v, monkeys}
    end
  end

  def ops("+"), do: fn a, b -> a + b end
  def ops("-"), do: fn a, b -> a - b end
  def ops("*"), do: fn a, b -> a * b end
  def ops("/"), do: fn a, b -> div(a, b) end

  def apply_op(op, {:str, _} = a, b) do
    {:str, {op, a, b}}
  end

  def apply_op(op, a, {:str, _} = b) do
    {:str, {op, a, b}}
  end

  def apply_op(op, {:done, a}, {:done, b}), do: {:done, ops(op).(a, b)}

  defp parse(lines) do
    lines
    |> Enum.map(fn line ->
      [name, eq] = String.split(line, ": ")

      case Integer.parse(eq) do
        :error ->
          [a, op, b] = String.split(eq, " ", trim: true)
          {name, {:op, op, a, b}}

        {v, _} ->
          {name, {:done, v}}
      end
    end)
    |> Enum.into(%{})
  end
end

DayTwentyOne.solve2(Kino.Input.read(input))