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

Day 11: Monkey in the Middle

2022/day_11.livemd

Day 11: Monkey in the Middle

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Part 1

Run in Livebook

https://adventofcode.com/2022/day/11

[monkeys, items] =
  input
  |> Kino.Input.read()
  |> String.split("\n\n", trim: true)
  |> Enum.map(&(&1 |> String.split("\n", trim: true)))
  |> Enum.map(fn [line0, line1, line2, line3, line4, line5] ->
    %{"id" => id} = Regex.named_captures(~r/Monkey (?\d+):/, line0)

    %{"starting_items_str" => starting_items_str} =
      Regex.named_captures(~r/Starting items: (?.+)$/, line1)

    starting_items =
      starting_items_str |> String.split(", ") |> Enum.map(&(&1 |> String.to_integer()))

    %{"operation_str" => operation_str} =
      Regex.named_captures(~r/Operation: new = (?.+)$/, line2)

    {operation, _} = ("fn old -> " <> operation_str <> " end") |> Code.eval_string()

    %{"divisible_by_str" => divisible_by_str} =
      Regex.named_captures(~r/Test: divisible by (?\d+)$/, line3)

    divisible_by = divisible_by_str |> String.to_integer()

    %{"if_true_id" => if_true_id} =
      Regex.named_captures(~r/If true: throw to monkey (?\d+)/, line4)

    %{"if_false_id" => if_false_id} =
      Regex.named_captures(~r/If false: throw to monkey (?\d+)/, line5)

    [{id, {operation, divisible_by, if_true_id, if_false_id}}, {id, starting_items}]
  end)
  |> Enum.zip_with(&amp; &amp;1)
  |> Enum.map(&amp;(&amp;1 |> Map.new()))

defmodule Solver.Part1 do
  def proceed_rounds(monkeys, items, round_count) do
    1..round_count
    |> Enum.reduce({items, %{}}, fn _, {items, inspect_counts} ->
      proceed_round(monkeys, items, inspect_counts)
    end)
  end

  defp proceed_round(monkeys, items, inspect_counts) do
    monkeys
    |> Enum.reduce({items, inspect_counts}, fn monkey, {items, inspect_counts} ->
      inspect_items(monkey, items, inspect_counts)
    end)
  end

  defp inspect_items({id, {operation, _, _, _}} = monkey, items, inspect_counts) do
    items_of_monkey = items |> Map.get(id)
    items = items |> Map.put(id, [])

    items_of_monkey
    |> Enum.reduce({items, inspect_counts}, fn item_of_monkey, {items, inspect_counts} ->
      worry_level = operation.(item_of_monkey)
      new_item = div(worry_level, 3)
      target_id = get_target_id(monkey, new_item)

      new_items = items |> Map.update!(target_id, &amp;[new_item | &amp;1])
      new_inspect_counts = inspect_counts |> Map.update(id, 1, &amp;(&amp;1 + 1))

      {new_items, new_inspect_counts}
    end)
  end

  defp get_target_id({_id, {_operation, divisible_by, if_true_id, if_false_id}}, worry_level) do
    case rem(worry_level, divisible_by) do
      0 -> if_true_id
      _ -> if_false_id
    end
  end
end

{_, inspect_counts} = Solver.Part1.proceed_rounds(monkeys, items, 20)

inspect_counts
|> Enum.map(fn {_, inspect_count} -> inspect_count end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.product()

Part 2

https://adventofcode.com/2022/day/11#part2

lcm =
  monkeys
  |> Enum.map(fn {_, {_, divisible_by, _, _}} -> divisible_by end)
  |> Enum.product()

defmodule Solver.Part2 do
  @lcm lcm

  def proceed_rounds(monkeys, items, round_count) do
    1..round_count
    |> Enum.reduce({items, %{}}, fn _, {items, inspect_counts} ->
      proceed_round(monkeys, items, inspect_counts)
    end)
  end

  defp proceed_round(monkeys, items, inspect_counts) do
    monkeys
    |> Enum.reduce({items, inspect_counts}, fn monkey, {items, inspect_counts} ->
      inspect_items(monkey, items, inspect_counts)
    end)
  end

  defp inspect_items({id, {operation, _, _, _}} = monkey, items, inspect_counts) do
    items_of_monkey = items |> Map.get(id)
    items = items |> Map.put(id, [])

    items_of_monkey
    |> Enum.reduce({items, inspect_counts}, fn item_of_monkey, {items, inspect_counts} ->
      worry_level = operation.(item_of_monkey)
      new_item = rem(worry_level, @lcm)
      target_id = get_target_id(monkey, new_item)

      new_items = items |> Map.update!(target_id, &amp;[new_item | &amp;1])
      new_inspect_counts = inspect_counts |> Map.update(id, 1, &amp;(&amp;1 + 1))

      {new_items, new_inspect_counts}
    end)
  end

  defp get_target_id({_id, {_operation, divisible_by, if_true_id, if_false_id}}, worry_level) do
    case rem(worry_level, divisible_by) do
      0 -> if_true_id
      _ -> if_false_id
    end
  end
end

{_, inspect_counts} = Solver.Part2.proceed_rounds(monkeys, items, 10000)

inspect_counts
|> Enum.map(fn {_, inspect_count} -> inspect_count end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.product()