Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2023

2023/day19.livemd

Advent of Code 2023

Mix.install([
  {:req, "~> 0.3.2"}
])

Day 19

input =
  "https://adventofcode.com/2023/day/19/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
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}
"""
defmodule A do
  def parse(input) do
    input
    |> String.split("\n\n", trim: true)
    |> Enum.map(&amp;String.split(&amp;1, "\n", trim: true))
    |> then(fn [workflows, parts] ->
      workflows =
        workflows
        |> Enum.map(fn line ->
          [_, id, rules] = Regex.run(~r/^(.*){(.*)}$/, line)

          rules =
            String.split(rules, ",")
            |> Enum.map(fn rule ->
              cond do
                String.contains?(rule, "<") ->
                  [id, n, action] = Regex.split(~r/[<:]/, rule)
                  {:lt, id, String.to_integer(n), action}

                String.contains?(rule, ">") ->
                  [id, n, action] = Regex.split(~r/[>:]/, rule)
                  {:gt, id, String.to_integer(n), action}

                true ->
                  rule
              end
            end)

          {id, rules}
        end)
        |> Enum.into(%{})

      parts =
        parts
        |> Enum.map(fn line ->
          Regex.run(~r/^{(x)=(\d+),(m)=(\d+),(a)=(\d+),(s)=(\d+)}$/, line)
          |> tl()
          |> Enum.chunk_every(2)
          |> Enum.map(fn [k, v] -> {k, String.to_integer(v)} end)
          |> Enum.into(Map.new())
        end)

      {workflows, parts}
    end)
  end

  def sort_part(_p, _workflows, ["A"]), do: :accept
  def sort_part(_p, _workflows, ["R"]), do: :reject

  def sort_part(p, workflows, [workflow_id]),
    do: sort_part(p, workflows, Map.fetch!(workflows, workflow_id))

  def sort_part(p, workflows, [rule | rules]) do
    case rule do
      {:lt, k, n, r} ->
        if p[k] < n do
          sort_part(p, workflows, [r])
        else
          sort_part(p, workflows, rules)
        end

      {:gt, k, n, r} ->
        if p[k] > n do
          sort_part(p, workflows, [r])
        else
          sort_part(p, workflows, rules)
        end
    end
  end

  defp distinct_ratings(_workflows, rating, ["A"]), do: [rating]
  defp distinct_ratings(_workflows, _rating, ["R"]), do: []

  defp distinct_ratings(workflows, rating, [id]),
    do: distinct_ratings(workflows, rating, workflows[id])

  defp distinct_ratings(workflows, rating, [{:lt, k, n, r} | rules]) do
    min..max//_ = rating[k]

    {lower, upper} =
      if min <= n &amp;&amp; n <= max do
        {min..(n - 1), n..max}
      else
        raise "boom"
      end

    distinct_ratings(workflows, Map.put(rating, k, lower), [r]) ++
      distinct_ratings(workflows, Map.put(rating, k, upper), rules)
  end

  defp distinct_ratings(workflows, rating, [{:gt, k, n, r} | rules]) do
    min..max//_ = rating[k]

    distinct_ratings(workflows, Map.put(rating, k, (n + 1)..max), [r]) ++
      distinct_ratings(workflows, Map.put(rating, k, min..n), rules)
  end

  def part1(input) do
    {workflows, parts} =
      input
      |> parse()

    parts
    |> Enum.map(fn part ->
      {sort_part(part, workflows, Map.fetch!(workflows, "in")), part}
    end)
    |> Enum.group_by(&amp;elem(&amp;1, 0))
    |> Map.get(:accept)
    |> Enum.flat_map(&amp;Map.values(elem(&amp;1, 1)))
    |> Enum.sum()
  end

  def part2(input) do
    {workflows, _} =
      input
      |> parse()

    rating =
      %{
        "x" => 1..4000,
        "m" => 1..4000,
        "a" => 1..4000,
        "s" => 1..4000
      }

    distinct_ratings(workflows, rating, ["in"])
    |> Enum.map(fn ratings ->
      ratings
      |> Map.values()
      |> Enum.map(&amp;Range.size/1)
      |> Enum.product()
    end)
    |> Enum.sum()
  end
end

Part 1

input
|> A.part1()

Part 2

input
|> A.part2()
graph TD;
  in-->in1{s < 1351?}
  in1-->|x=1..4000\nm=1..4000\na=1..4000\ns=1..1350| px
  in1-->|x=1..4000\nm=1..4000\na=1..4000\ns=1351..4000| qqz
  px-->px1{a<2006?}
  px1-->|x=1..4000\nm=1..4000\na=1..2005\ns=1..1350| qkq
  px1-->|x=1..4000\nm=1..4000\na=2006..4000\ns=1..1350| px2{m<1801?}
  px2-->|x=1..4000\nm=1..1800\na=2006..4000\ns=1..1350| hdj
  px2-->reject
  hdj-->hdj1{m>838?}
  hdj1-->|x=1..4000\nm=839..1800\na=2006..4000\ns=1..1350| accept
  hdj1-->pv