Day 11: Monkey in the Middle
Mix.install([:kino])
input = Kino.Input.textarea("Please paste your input:")
Part 1
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(& &1)
|> Enum.map(&(&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, &[new_item | &1])
new_inspect_counts = inspect_counts |> Map.update(id, 1, &(&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, &[new_item | &1])
new_inspect_counts = inspect_counts |> Map.update(id, 1, &(&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()