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

AoC 2022 - day 11

tradfursten-elixir/day-11.livemd

AoC 2022 - day 11

Mix.install([:kino])

Section

input = Kino.Input.textarea("input")
defmodule Monkey do
  defstruct id: nil, items: [], op: nil, divider: 1, true: nil, false: nil, inspected: 0
end

defmodule Day01 do
  def solve1(input) do
    monkeys =
      input
      |> String.split("\n")
      |> Enum.chunk_by(&(&1 != ""))
      |> Enum.filter(&(&1 != [""]))
      |> Enum.map(&parse_monkey(&1, %Monkey{}))
      |> Enum.reduce(%{}, fn monkey, acc ->
        Map.put(acc, monkey.id, monkey)
      end)

    1..20
    |> Enum.reduce(monkeys, fn _round, acc -> play_round(acc, 0, 3) end)
    |> Map.to_list()
    |> Enum.map(fn {_k, v} -> v.inspected end)
    |> Enum.sort(fn a, b -> a > b end)
    |> Enum.take(2)
    |> Enum.product()
  end

  def solve2(input) do
    monkeys =
      input
      |> String.split("\n")
      |> Enum.chunk_by(&(&1 != ""))
      |> Enum.filter(&(&1 != [""]))
      |> Enum.map(&parse_monkey(&1, %Monkey{}))
      |> Enum.reduce(%{}, fn monkey, acc ->
        Map.put(acc, monkey.id, monkey)
      end)

    divider =
      monkeys
      |> Map.values()
      |> Enum.map(fn monkey -> monkey.divider end)
      |> Enum.product()

    1..10000
    |> Enum.reduce(monkeys, fn _round, acc -> play_round(acc, 0, divider) end)
    |> Map.to_list()
    |> Enum.map(fn {_k, v} -> v.inspected end)
    |> Enum.sort(fn a, b -> a > b end)
    |> Enum.take(2)
    |> Enum.product()
  end

  defp parse_monkey([], monkey), do: monkey

  defp parse_monkey(["Monkey " <> rest | tail], monkey) do
    id =
      rest
      |> String.replace(":", "")
      |> String.to_integer()

    parse_monkey(tail, %{monkey | id: id})
  end

  defp parse_monkey(["  Starting items: " <> rest | tail], monkey) do
    items =
      rest
      |> String.split(", ")
      |> Enum.map(fn item ->
        String.to_integer(item)
      end)

    parse_monkey(tail, %{monkey | items: items})
  end

  defp parse_monkey(["  Operation: " <> rest | tail], monkey) do
    parts = String.split(rest, " ")

    f =
      case Enum.at(parts, 3) do
        "*" -> &amp;Kernel.*/2
        "+" -> &amp;Kernel.+/2
      end

    a = Integer.parse(Enum.at(parts, 2))
    b = Integer.parse(Enum.at(parts, 4))

    op =
      cond do
        a == :error and b == :error -> &amp;f.(&amp;1, &amp;1)
        a == :error -> &amp;f.(&amp;1, elem(b, 0))
        b == :error -> &amp;f.(elem(a, 0), &amp;1)
      end

    parse_monkey(tail, %{monkey | op: op})
  end

  defp parse_monkey(["  Test: divisible by " <> rest | tail], monkey) do
    test_value = String.to_integer(rest)
    parse_monkey(tail, %{monkey | divider: test_value})
  end

  defp parse_monkey(["    If true: throw to monkey " <> rest | tail], monkey) do
    parse_monkey(tail, %{monkey | true: String.to_integer(rest)})
  end

  defp parse_monkey(["    If false: throw to monkey " <> rest | tail], monkey) do
    parse_monkey(tail, %{monkey | false: String.to_integer(rest)})
  end

  defp play_round(monkeys, i, divider) do
    case Map.get(monkeys, i) do
      nil ->
        monkeys

      monkey ->
        # IO.puts("Monkey #{monkey.id}")
        monkeys = throw_items(monkey.items, monkey, monkeys, divider)
        monkey = %{monkey | items: [], inspected: monkey.inspected + length(monkey.items)}
        monkeys = Map.put(monkeys, i, monkey)
        play_round(monkeys, i + 1, divider)
    end
  end

  defp throw_items([], _, monkeys, _), do: monkeys

  defp throw_items([worry | items], monkey, monkeys, divider) do
    # IO.puts("Item worry #{worry}")
    worry = monkey.op.(worry)
    # IO.puts("Item worry after update #{worry}")
    worry =
      case divider do
        3 -> div(worry, 3)
        _ -> rem(worry, divider)
      end

    # IO.puts("Worry decrease #{worry}")
    target =
      case rem(worry, monkey.divider) == 0 do
        true -> monkey.true
        false -> monkey.false
      end

    target_monkey = Map.get(monkeys, target)
    target_monkey = %{target_monkey | items: target_monkey.items ++ [worry]}
    monkeys = Map.put(monkeys, target, target_monkey)
    throw_items(items, monkey, monkeys, divider)
  end
end
Kino.Input.read(input) |> Day01.solve1()
Kino.Input.read(input) |> Day01.solve2()