Powered by AppSignal & Oban Pro

Advent of Code 2023 Day 19 Part 2

2023/day-19-part-2.livemd

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)