aoc/23/elixir/lib/days/day20.ex
2023-12-23 15:35:11 +01:00

127 lines
4.1 KiB
Elixir

defmodule AOC.Day20 do
# use AOC.Day, day: 20, input: "example"
use AOC.Day, day: 20
def parse_input(lines) do
modules =
lines
|> Enum.map(fn line ->
[name, destination_modules] = String.split(line, " -> ")
destination_modules = String.split(destination_modules, ", ")
case String.at(line, 0) do
"%" -> {String.trim_leading(name, "%"), {:flip_flop, destination_modules, :off}}
"&" -> {String.trim_leading(name, "&"), {:conjunction, destination_modules, %{}}}
_ -> {name, {:broadcast, destination_modules}}
end
end)
|> Enum.into(%{})
Enum.reduce(modules, modules, fn {module_name, module}, acc ->
module
|> elem(1)
|> Enum.reduce(acc, fn destination_name, acc ->
case Map.fetch(acc, destination_name) do
{:ok, {:conjunction, destination_names, states} = destination_module} ->
Map.put(
acc,
destination_name,
{:conjunction, destination_names, Map.put(states, module_name, :low)}
)
_ ->
acc
end
end)
end)
end
def press_button(modules) do
pulse_queue = :queue.new()
pulse_queue = :queue.in({:low, "broadcaster", "button"}, pulse_queue)
propagate_pulses(modules, pulse_queue, {0, 0})
end
def send_pulses(pulse_queue, pulse_strength, destination_names, from) do
Enum.reduce(destination_names, pulse_queue, fn destination_name, acc ->
:queue.in({pulse_strength, destination_name, from}, acc)
end)
end
def propagate_pulses(modules, pulse_queue, {low_count, high_count}) do
case :queue.out(pulse_queue) do
{:empty, _} ->
{modules, {low_count, high_count}}
{{:value, {pulse_strength, module_name, from}}, pulse_queue} ->
{modules, pulse_queue} =
case Map.fetch(modules, module_name) do
:error ->
{modules, pulse_queue}
{:ok, {:broadcast, destination_names}} ->
pulse_queue =
send_pulses(pulse_queue, pulse_strength, destination_names, module_name)
{modules, pulse_queue}
{:ok, {:flip_flop, destination_names, state}} ->
case pulse_strength do
:high ->
{modules, pulse_queue}
:low ->
case state do
:off ->
module = {:flip_flop, destination_names, :on}
pulse_queue =
send_pulses(pulse_queue, :high, destination_names, module_name)
{Map.put(modules, module_name, module), pulse_queue}
:on ->
module = {:flip_flop, destination_names, :off}
pulse_queue = send_pulses(pulse_queue, :low, destination_names, module_name)
{Map.put(modules, module_name, module), pulse_queue}
end
end
{:ok, {:conjunction, destination_names, state}} ->
state = Map.put(state, from, pulse_strength)
modules = Map.put(modules, module_name, {:conjunction, destination_names, state})
all_high? = state |> Map.values() |> Enum.all?(&(&1 == :high))
pulse_queue =
if all_high? do
send_pulses(pulse_queue, :low, destination_names, module_name)
else
send_pulses(pulse_queue, :high, destination_names, module_name)
end
{modules, pulse_queue}
end
low_count = low_count + if pulse_strength == :low, do: 1, else: 0
high_count = high_count + if pulse_strength == :high, do: 1, else: 0
propagate_pulses(modules, pulse_queue, {low_count, high_count})
end
end
def part1(modules) do
{_, {lows, highs}} =
Enum.reduce(1..1000, {modules, {0, 0}}, fn _, {modules, {low_acc, high_acc}} ->
{modules, {low, high}} = press_button(modules)
{modules, {low_acc + low, high_acc + high}}
end)
lows * highs
end
def part2(_input) do
"TODO"
end
end