Day 11: Monkey in the Middle
Mix.install(nimble_parsec: ">= 1.2.3")
text = File.read!("Code/aoc.ex/input/2022/11.txt")
AoC
Parser
defmodule MonkeyMaster do
defmodule Parser do
import NimbleParsec
monkey =
ignore(repeat(ascii_char(not: ?0..?9)))
|> unwrap_and_tag(integer(min: 1), :index)
|> ignore(repeat(ascii_char(not: ?0..?9)))
|> unwrap_and_tag(integer(min: 1), :start)
|> repeat(ignore(string(", ")) |> unwrap_and_tag(integer(min: 1), :start))
|> ignore(repeat(ascii_char(not: ?=..?=)))
|> ignore(string("= "))
|> unwrap_and_tag(ascii_string([(?\n + 1)..255], min: 1), :op)
|> ignore(repeat(ascii_char(not: ?0..?9)))
|> unwrap_and_tag(integer(min: 1), :divisor)
|> ignore(repeat(ascii_char(not: ?0..?9)))
|> unwrap_and_tag(integer(min: 1), :if_true)
|> ignore(repeat(ascii_char(not: ?0..?9)))
|> unwrap_and_tag(integer(min: 1), :if_false)
input = wrap(monkey) |> repeat(ignore(string("\n\n")) |> wrap(monkey))
defparsec(:input, input)
end
defp toss({idx, item, round}, data, adjuster) do
monkey = elem(data, idx)
new_item = item |> then(monkey[:op]) |> then(adjuster)
new_idx = monkey[if(rem(new_item, monkey[:divisor]) == 0, do: :if_true, else: :if_false)]
new_round = if new_idx < idx, do: round + 1, else: round
{new_idx, new_item, new_round}
end
def run(data, mode, rounds) do
data =
data
|> MonkeyMaster.Parser.input()
|> elem(1)
|> update_in(
[Access.all(), Access.filter(&match?({:op, _}, &1)), Access.elem(1)],
&("fn old -> #{&1} end" |> Code.eval_string() |> elem(0))
)
lcm = data |> Enum.map(& &1[:divisor]) |> Enum.reduce(&Kernel.*/2)
adjuster =
case mode do
:angrily -> &rem(&1, lcm)
:nicely -> &div(&1, 3)
end
monkey_tuple = List.to_tuple(data)
for monkey <- data,
item <- Keyword.get_values(monkey, :start),
stream = Stream.iterate({monkey[:index], item, 0}, &toss(&1, monkey_tuple, adjuster)),
result <- Enum.take_while(stream, &(elem(&1, 2) < rounds)) do
result
end
|> Enum.frequencies_by(&elem(&1, 0))
|> Map.values()
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.reduce(&Kernel.*/2)
end
end
example = """
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
"""
MonkeyMaster.run(example, :nicely, 20)
MonkeyMaster.run(example, :angrily, 10_000)
"Code/aoc.ex/input/2022/11.txt"
|> File.read!()
|> MonkeyMaster.run(:nicely, 20)
"Code/aoc.ex/input/2022/11.txt"
|> File.read!()
|> MonkeyMaster.run(:angrily, 10_000)