Powered by AppSignal & Oban Pro

Day 08

2025/day08.livemd

Day 08

Mix.install([:kino_aoc])

Parse

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2025", "8", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
boxes =
  puzzle_input
  |> String.split()
  |> Enum.map(fn raw ->
    raw
    |> String.split(",")
    |> Enum.map(&String.to_integer/1)
    |> List.to_tuple()
  end)
  |> Enum.sort()

Setup

defmodule JunctionBoxes do
  def all_pairs([]), do: []

  def all_pairs([x | rest]) do
    for(y <- rest, do: {x, y}) ++ all_pairs(rest)
  end

  def dist2({ax, ay, az}, {bx, by, bz}) do
    dx = ax - bx
    dy = ay - by
    dz = az - bz

    dx ** 2 + dy ** 2 + dz ** 2
  end

  def group([], {a, b}), do: [MapSet.new([a, b])]
  def group([set | rest], {a, b}) do
    cond do
      a in set and b in set -> [set | rest]
      a in set or b in set -> set |> MapSet.put(a) |> MapSet.put(b) |> do_squash(rest, a, b, [])
      true -> [set | group(rest, {a, b})]
    end
  end

  defp do_squash(curr, [], _, _, acc), do: [curr | acc]
  defp do_squash(curr, [x | rest], a, b, acc) do
    if a in x or b in x do
      [MapSet.union(curr, x) | acc ++ rest]
    else
      do_squash(curr, rest, a, b, [x | acc])
    end
  end
end
sorted_pairs =
  JunctionBoxes.all_pairs(boxes)
  |> Enum.sort_by(fn {a, b} -> JunctionBoxes.dist2(a, b) end)

Part 1

sorted_pairs
|> Enum.take(1000)
|> Enum.reduce([], &amp;JunctionBoxes.group(&amp;2, &amp;1))
|> Enum.map(&amp;MapSet.size/1)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.product()

Part 2

box_count = length(boxes)
{a, b} =
  sorted_pairs
  |> Enum.reduce_while([], fn pair, acc ->
    new_acc = JunctionBoxes.group(acc, pair)

    if MapSet.size(hd(new_acc)) == box_count do
      {:halt, pair}
    else
      {:cont, new_acc}
    end
  end)
{ax, _, _} = a
{bx, _, _} = b

ax * bx