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

Advent of Code 2023 Day 19

2023/19.livemd

Advent of Code 2023 Day 19

Mix.install([
  {:kino, "~> 0.12.0"}
])

Section

input = Kino.Input.textarea("input")
defmodule Workflow do
  def process_rule(rule) do
    case String.contains?(rule, ":") do
      true ->
        split =
          rule
          |> String.split(":", trim: true)

        {String.split(Enum.at(split, 0), ~r/\<|\>/, include_captures: true), Enum.at(split, 1)}

      false ->
        rule
    end
  end

  def process_route(route) do
    route_parsed =
      route
      |> String.split(~r/{|}/, trim: true)

    %{
      Enum.at(route_parsed, 0) =>
        Enum.map(String.split(Enum.at(route_parsed, 1), ",", trim: true), fn rule ->
          process_rule(rule)
        end)
    }
  end

  def process_routes(routes) do
    routes
    |> Enum.reduce(%{}, fn route, acc -> Map.merge(acc, process_route(route)) end)
  end

  def process_weight(weight) do
    weight
    |> String.replace(~r/\{|\}/, "")
    |> String.split(",", trim: true)
    |> Enum.map(fn weight -> String.split(weight, "=") end)
    |> Map.new(fn [k, v] -> {k, String.to_integer(v)} end)
  end

  def process_weights(weights) do
    weights
    |> Enum.map(fn weight -> process_weight(weight) end)
  end

  def process([routes, weights] = _) do
    [
      process_routes(routes),
      process_weights(weights)
    ]
  end

  def parse(input) do
    Kino.Input.read(input)
    |> String.split("\n\n", trim: true)
    |> Enum.map(fn part -> String.split(part, "\n", trim: true) end)
    |> process
  end

  def compare(param, "<", val), do: param < String.to_integer(val)
  def compare(param, ">", val), do: param > String.to_integer(val)

  def walk(routes, key, weight) do
    rule =
      Enum.find(Map.get(routes, key), fn rule ->
        if is_tuple(rule) do
          [param, cond, value] = elem(rule, 0)
          if compare(Map.get(weight, param), cond, value), do: true, else: false
        else
          false
        end
      end)

    action = if rule, do: elem(rule, 1), else: Enum.at(Map.get(routes, key), -1)

    case action do
      "A" -> true
      "R" -> false
      _ -> walk(routes, action, weight)
    end
  end

  def p1(input) do
    [routes, weights] = parse(input)

    weights
    |> Enum.filter(fn weight -> walk(routes, "in", weight) end)
    |> Enum.flat_map(fn el -> Map.values(el) end)
    |> Enum.sum()
  end
end
Workflow.parse(input)
Workflow.p1(input)