Day 11
Mix.install([
{:req, "~> 0.3.2"},
{:vega_lite, "~> 0.1.6"},
{:kino_vega_lite, "~> 0.1.6"}
])
alias VegaLite, as: Vl
day = 11
aoc_session = System.fetch_env!("LB_AOC_SESSION")
input_url = "https://adventofcode.com/2022/day/#{day}/input"
{:ok, %{body: input}} = Req.get(input_url, headers: [cookie: "session=#{aoc_session}"])
Input
test_input = """
Monkey 0:
Starting items: 79, 98
Operation: new = old * 19
Test: divisible by 23
If true: throw to monkey 2
If false: throw to monkey 3
Monkey 1:
Starting items: 54, 65, 75, 74
Operation: new = old + 6
Test: divisible by 19
If true: throw to monkey 2
If false: throw to monkey 0
Monkey 2:
Starting items: 79, 60, 97
Operation: new = old * old
Test: divisible by 13
If true: throw to monkey 1
If false: throw to monkey 3
Monkey 3:
Starting items: 74
Operation: new = old + 3
Test: divisible by 17
If true: throw to monkey 0
If false: throw to monkey 1
"""
defmodule Monkey do
defstruct id: 0,
items: [],
op: nil,
mod: 1,
test: nil,
true_target: 0,
false_target: 0,
inspections: 0
def inspect(monkey, worry_level, gcd, worry_divisor \\ 3) do
worry_level = (monkey.op.(worry_level) / worry_divisor) |> floor()
worry_level = rem(worry_level, gcd)
target = if monkey.test.(worry_level), do: monkey.true_target, else: monkey.false_target
m =
monkey
|> Map.put(:items, Enum.drop(monkey.items, 1))
|> Map.put(:inspections, monkey.inspections + 1)
{m, worry_level, target}
end
end
defmodule Input do
def load(input) do
for monkey_str <- String.split(input, "\n\n", trim: true), into: %{} do
monkey = parse_monkey_str(monkey_str)
{monkey.id, monkey}
end
end
def parse_monkey_str(monkey_str) do
monkey_str
|> String.split("\n", trim: true)
|> Enum.map(&String.trim/1)
|> Enum.reduce(%Monkey{}, fn
"Monkey " <> n, monkey ->
%{monkey | id: parse_id(n)}
"Starting items: " <> items, monkey ->
%{monkey | items: parse_items(items)}
"Operation: new = old * old", monkey ->
%{monkey | op: fn x -> x * x end}
"Operation: new = old " <> op, monkey ->
%{monkey | op: parse_op(op)}
"Test: divisible by " <> n, monkey ->
monkey |> Map.put(:test, parse_test(n)) |> Map.put(:mod, String.to_integer(n))
"If true: throw to monkey " <> n, monkey ->
%{monkey | true_target: String.to_integer(n)}
"If false: throw to monkey " <> n, monkey ->
%{monkey | false_target: String.to_integer(n)}
end)
end
def parse_id(str), do: str |> String.at(0) |> String.to_integer()
def parse_test(modulo) do
fn x -> rem(x, String.to_integer(modulo)) == 0 end
end
def parse_items(str) do
str
|> String.split(", ", trim: true)
|> Enum.map(&String.to_integer/1)
end
def parse_op("* " <> n), do: fn old -> old * String.to_integer(n) end
def parse_op("+ " <> n), do: fn old -> old + String.to_integer(n) end
end
Part 1
defmodule MonkeyBusiness do
def calc(input, num_rounds, worry_divisor) do
monkeys = Input.load(input)
gcd = get_gcd(monkeys)
1..num_rounds
|> Enum.reduce(monkeys, fn _, monkeys -> do_round(monkeys, gcd, worry_divisor) end)
|> Enum.map(fn {_, m} -> m.inspections end)
|> Enum.sort()
|> Enum.reverse()
|> Enum.take(2)
|> Enum.product()
end
def do_round(monkeys, gcd, worry_divisor) do
ids = Map.keys(monkeys) |> Enum.sort()
Enum.reduce(ids, monkeys, fn i, monkeys ->
m = monkeys[i]
Enum.reduce(m.items, monkeys, fn item, monkeys ->
m = monkeys[i]
{m, worry_level, id} = Monkey.inspect(m, item, gcd, worry_divisor)
monkeys = Map.put(monkeys, m.id, %{m | items: Enum.drop(m.items, 1)})
new_items = monkeys[id].items ++ [worry_level]
new_monkey = %{monkeys[id] | items: new_items}
Map.put(monkeys, id, new_monkey)
end)
end)
end
def get_gcd(monkeys) do
monkeys
|> Enum.map(fn {_id, m} -> m.mod end)
|> Enum.product()
end
end
MonkeyBusiness.calc(input, 20, 3)
Part 2
MonkeyBusiness.calc(input, 10_000, 1)