Skip to content

Instantly share code, notes, and snippets.

@reverie
Last active December 12, 2022 03:42
Show Gist options
  • Save reverie/dad1c3bff03b4740ef29ce21fbf16097 to your computer and use it in GitHub Desktop.
Save reverie/dad1c3bff03b4740ef29ce21fbf16097 to your computer and use it in GitHub Desktop.
defmodule BasicMath do
# From https://programming-idioms.org/idiom/75/compute-lcm/983/elixir
def gcd(a, 0), do: a
def gcd(0, b), do: b
def gcd(a, b), do: gcd(b, rem(a, b))
def lcm(0, 0), do: 0
def lcm(a, b), do: div(a * b, gcd(a, b))
end
defmodule Main do
def run() do
"input.txt"
|> File.read!()
|> solve()
end
def solve(data) do
monkeys =
data
|> String.split("\n\n")
|> Enum.map(&parse_block/1)
|> Enum.map(&Map.put(&1, :num_inspects, 0))
|> Enum.with_index()
|> Map.new(fn {monkey, idx} -> {idx, monkey} end)
part1(monkeys) |> IO.inspect(charlists: :as_lists)
part2(monkeys) |> IO.inspect(charlists: :as_lists)
end
def part1(monkeys) do
worry_reliever = fn x -> floor(x / 3) end
final_state = run_all_rounds(monkeys, worry_reliever, 20)
monkey_business(final_state)
end
def part2(monkeys) do
lcm_of_divisors =
monkeys
|> Enum.map(fn {_, monkey} -> monkey.divisor end)
|> Enum.reduce(&BasicMath.lcm/2)
worry_reliever = fn x -> rem(x, lcm_of_divisors) end
final_state = run_all_rounds(monkeys, worry_reliever, 10_000)
monkey_business(final_state)
end
def parse_block(block) do
[_ | lines] = String.split(block, "\n", trim: true)
[items_l, operations_l, divisor_l, true_l, false_l] = lines
[_, operation_s] = String.split(operations_l, ": ")
operation_fn_s = String.replace(operation_s, "new =", "fn (old) ->") <> " end"
{operation, _} = Code.eval_string(operation_fn_s)
%{
items: ints(items_l),
operation: operation,
divisor: hd(ints(divisor_l)),
true_id: hd(ints(true_l)),
false_id: hd(ints(false_l))
}
end
def ints(string) do
regex = ~r/\d+/
matches = Regex.scan(regex, string)
Enum.map(List.flatten(matches), &String.to_integer/1)
end
def run_all_rounds(monkeys, worry_reliever, num_rounds) do
Enum.reduce(1..num_rounds, monkeys, fn _, acc ->
run_a_round(acc, worry_reliever)
end)
end
def run_a_round(monkeys, worry_reliever) do
Enum.reduce(monkeys, monkeys, fn {monkey_id, _monkey}, acc ->
do_monkey_throws(acc, worry_reliever, monkey_id)
end)
end
def do_monkey_throws(monkeys, worry_reliever, monkey_id) do
Enum.reduce(monkeys[monkey_id].items, monkeys, fn item, acc ->
thrower = acc[monkey_id]
{receiver_id, new_item} = get_receiver(thrower, worry_reliever, item)
receiver = Map.update!(acc[receiver_id], :items, &[new_item | &1])
thrower = %{thrower | items: [], num_inspects: thrower.num_inspects + 1}
%{acc | monkey_id => thrower, receiver_id => receiver}
end)
end
def get_receiver(monkey, worry_reliever, item) do
item = monkey.operation.(item)
item = worry_reliever.(item)
case rem(item, monkey.divisor) == 0 do
true -> {monkey.true_id, item}
false -> {monkey.false_id, item}
end
end
def monkey_business(monkeys) do
# the product of the number of inspections of the two most active monkeys
monkeys
|> Enum.map(fn {_, monkey} -> monkey.num_inspects end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.product()
end
end
Main.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment