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

Day 21: Monkey Math

2022/day-21.livemd

Day 21: Monkey Math

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

Day 21

sample_input = Kino.Input.textarea("Paste Sample Input")
real_input = Kino.Input.textarea("Paste Real Input")
defmodule Equation do
  defstruct first: nil, second: nil, operation: nil

  def new(first, second, operation),
    do: %__MODULE__{first: first, second: second, operation: operation}

  def operation(%__MODULE__{operation: op}), do: operation(op)

  def operation(op) when is_binary(op) do
    case op do
      "+" -> fn a, b -> a + b end
      "-" -> fn a, b -> a - b end
      "/" -> fn a, b -> a / b end
      "*" -> fn a, b -> a * b end
    end
  end
end
defmodule Expression do
  defstruct type: nil, value: nil

  def new(number) when is_integer(number), do: %__MODULE__{type: :number, value: number}

  def new(func) when is_function(func), do: %__MODULE__{type: :function, value: func}

  def combine(%{type: :number, value: one}, op, %{type: :number, value: two}),
    do: %__MODULE__{type: :number, value: Equation.operation(op).(one, two)}

  def combine(%{type: :function, value: one}, op, %{type: :number, value: two}),
    do: %__MODULE__{
      type: :function,
      value: fn humn -> Equation.operation(op).(one.(humn), two) end
    }

  def combine(%{type: :number, value: one}, op, %{type: :function, value: two}),
    do: %__MODULE__{
      type: :function,
      value: fn humn -> Equation.operation(op).(one, two.(humn)) end
    }

  def combine(%{type: :function, value: one}, op, %{type: :function, value: two}),
    do: %__MODULE__{
      type: :function,
      value: fn humn -> Equation.operation(op).(one.(humn), two.(humn)) end
    }
end
defmodule State do
  defstruct resolved: %{}, equations: %{}, part2: false

  def new(input, part2 \\ false) do
    input
    |> Kino.Input.read()
    |> String.split("\n")
    |> Enum.reduce(%__MODULE__{part2: part2}, &process_monkey(&2, &1))
  end

  def process_monkey(state, <> <> ": " <> value) do
    case Integer.parse(value) do
      {integer, ""} -> %{state | resolved: Map.put(state.resolved, name, integer)}
      :error -> process_equation(state, name, value)
    end
  end

  def process_equation(
        state,
        name,
        <> <> " " <> <> <> " " <> <>
      ) do
    %{state | equations: Map.put(state.equations, name, Equation.new(a, b, op))}
  end

  def resolve_value(%{part2: true} = state, "humn") do
    {state, %Expression{type: :function, value: fn a -> a end}}
  end

  def resolve_value(state, name) do
    case Map.get(state.resolved, name) do
      nil ->
        {state, value} = resolve_equation(state, name)
        {%{state | resolved: Map.put(state.resolved, name, value)}, value}

      number ->
        {state, %Expression{type: :number, value: number}}
    end
  end

  def resolve_equation(state, name) do
    equation = Map.get(state.equations, name)
    {state, first_value} = resolve_value(state, equation.first)
    {state, second_value} = resolve_value(state, equation.second)
    {state, Expression.combine(first_value, equation.operation, second_value)}
  end
end
sample_input |> State.new() |> State.resolve_value("root") |> elem(1)
real_input |> State.new() |> State.resolve_value("root") |> elem(1)
%{value: lhs} = sample_input |> State.new(true) |> State.resolve_value("pppw") |> elem(1)
%{value: rhs} = sample_input |> State.new(true) |> State.resolve_value("sjmn") |> elem(1)
{lhs, rhs}
0..500 |> Enum.map(fn i -> {i, lhs.(i)} end) |> Enum.find(fn {_, val} -> val == 150 end)
%{value: lhs} = real_input |> State.new(true) |> State.resolve_value("prrg") |> elem(1)
%{value: rhs} = real_input |> State.new(true) |> State.resolve_value("jntz") |> elem(1)
{lhs, rhs}
start = 3_221_245_824_350

start..(start + 50)
|> Enum.map(fn i ->
  input = i
  val = lhs.(input)
  {input, val, val - 28_379_346_560_301}
end)
defmodule BinarySearch do
  def find(error, guess1, guess2) do
    test1 = error.(guess1)
    test2 = error.(guess2)
    middle = div(guess1 + guess2, 2)
    test_middle = error.(middle)

    cond do
      test1 == 0 -> guess1
      test2 == 0 -> guess2
      test_middle == 0 -> middle
      test1 * test_middle > 0 -> find(error, middle, guess2)
      true -> find(error, guess1, middle)
    end
  end
end

BinarySearch.find(
  fn guess -> lhs.(guess) - 28_379_346_560_301 end,
  3_200_000_000_000,
  3_300_000_000_000
)