Advent of Code 2023 Day 19 Part 2
Mix.install([
{:kino_aoc, "~> 0.1.5"}
])
Section
test_input =
"""
px{a<2006:qkq,m>2090:A,rfg}
pv{a>1716:R,A}
lnx{m>1548:A,A}
rfg{s<537:gd,x>2440:R,A}
qs{s>3448:A,lnx}
qkq{x<1416:A,crn}
crn{x>2662:A,R}
in{s<1351:px,qqz}
qqz{s>2770:qs,m<1801:hdj,R}
gd{a>3333:R,R}
hdj{m>838:A,pv}
{x=787,m=2655,a=1222,s=2876}
{x=1679,m=44,a=2067,s=496}
{x=2036,m=264,a=79,s=2244}
{x=2461,m=1339,a=466,s=291}
{x=2127,m=1623,a=2188,s=1013}
"""
|> String.trim()
{:ok, real_input} =
KinoAOC.download_puzzle("2023", "19", System.fetch_env!("LB_AOC_SESSION"))
puzzle_input = real_input
specs =
puzzle_input
|> String.split("\n\n", parts: 2)
|> hd()
|> String.split("\n")
defmodule Acceptor do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, 0, name: :A)
end
@impl true
def init(state) do
{:ok, state}
end
@impl true
def handle_cast({:handle, xmas}, sum) do
xmas
|> Map.values()
|> Stream.map(fn a..b ->
b - a + 1
end)
|> Stream.map(&max(&1, 0))
|> Enum.product()
|> then(&{:noreply, &1 + sum})
end
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
end
defmodule Rejector do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, nil, name: :R)
end
@impl true
def init(state) do
{:ok, state}
end
@impl true
def handle_cast({:handle, _xmas}, state) do
{:noreply, state}
end
end
defmodule Pipe do
use GenServer
def start_link(spec) do
{name, rules} = parse_spec(spec)
GenServer.start_link(__MODULE__, rules, name: name)
end
@impl true
def init(rules) do
{:ok, rules}
end
@impl true
def handle_cast({:handle, xmas}, rules) do
rules
|> Enum.reduce(xmas, fn rule, xmas ->
rule.(xmas)
end)
{:noreply, rules}
end
defp parse_spec(spec) do
[name, rules] =
~r/^(\w+)\{(.*)\}$/
|> Regex.run(spec, capture: :all_but_first)
name = String.to_atom(name)
rules =
rules
|> String.split(",")
|> Enum.map(&parse_rule/1)
{name, rules}
end
defp parse_rule(rule) do
case Regex.split(~r/\W/, rule, trim: true, include_captures: true) do
[next] ->
next = String.to_atom(next)
fn xmas ->
GenServer.cast(next, {:handle, xmas})
nil
end
[key, comp, thres, ":", next] ->
thres = String.to_integer(thres)
next = String.to_atom(next)
fn xmas ->
range = xmas[key]
{met, unmet} = split(comp, thres, range)
GenServer.cast(next, {:handle, %{xmas | key => met}})
%{xmas | key => unmet}
end
end
end
defp split("<", thres, a..b), do: {a..(thres - 1), thres..b}
defp split(">", thres, a..b), do: {(thres + 1)..b, a..thres}
end
Acceptor.start_link()
Rejector.start_link()
pipes =
for spec <- specs do
Pipe.start_link(spec)
end
GenServer.cast(
:in,
{:handle,
%{
"x" => 1..4000,
"m" => 1..4000,
"a" => 1..4000,
"s" => 1..4000
}}
)
GenServer.call(:A, :get)