Powered by AppSignal & Oban Pro

Day 8: Playground

2025/day-08.livemd

Day 8: Playground

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

Day 8

sample_input = Kino.Input.textarea("Paste Sample Input")
real_input = Kino.Input.textarea("Paste Real Input")
defmodule JunctionBoxes do
  def part1(input, connection_count) do
    points =
      input
      |> Kino.Input.read()
      |> String.split("\n", trim: true)
      |> Enum.map(fn line -> "{#{line}}" |> Code.eval_string() |> elem(0) end)

    for p1 <- points, p2 <- points, p1 < p2, reduce: [] do
      distances -> [{p1, p2, delta_squared(p1, p2)} | distances]
    end
    |> Enum.sort_by(fn {_p1, _p2, d} -> d end)
    |> Enum.reduce_while({0, %{}, %{}}, fn {p1, p2, _d}, {count, box_circuits, circuits} ->
      already_connected? =
        !is_nil(Map.get(box_circuits, p1)) and
          Map.get(box_circuits, p1) == Map.get(box_circuits, p2)

      cond do
        count >= connection_count ->
          {:halt, IO.inspect(circuits)}

        already_connected? ->
          IO.inspect(
            "skipping connection between connected boxes #{inspect(p1)} and #{inspect(p2)}"
          )

          {:cont, {count + 1, box_circuits, circuits}}

        true ->
          IO.inspect("Connecting boxes #{inspect(p1)} and #{inspect(p2)}")
          circuit_1_id = Map.get(box_circuits, p1)
          circuit_2_id = Map.get(box_circuits, p2)
          circuit_1 = if circuit_1_id, do: Map.get(circuits, circuit_1_id), else: MapSet.new([p1])
          circuit_2 = if circuit_2_id, do: Map.get(circuits, circuit_2_id), else: MapSet.new([p2])

          circuit_id = circuit_1_id || circuit_2_id || System.unique_integer()
          circuit_boxes = MapSet.union(circuit_1, circuit_2)

          circuits =
            circuits
            |> Map.delete(circuit_1_id)
            |> Map.delete(circuit_2_id)
            |> Map.put(circuit_id, circuit_boxes)

          box_circuits = Enum.reduce(circuit_boxes, box_circuits, &amp;Map.put(&amp;2, &amp;1, circuit_id))
          {:cont, {count + 1, box_circuits, circuits}}
      end
    end)
    |> Enum.map(fn {_id, circuit} -> MapSet.size(circuit) end)
    |> Enum.sort(:desc)
    |> Enum.take(3)
    |> Enum.reduce(1, &amp;(&amp;1 * &amp;2))
  end

  def part2(input) do
    points =
      input
      |> Kino.Input.read()
      |> String.split("\n", trim: true)
      |> Enum.map(fn line -> "{#{line}}" |> Code.eval_string() |> elem(0) end)

    point_count = length(points)

    for p1 <- points, p2 <- points, p1 < p2, reduce: [] do
      distances -> [{p1, p2, delta_squared(p1, p2)} | distances]
    end
    |> Enum.sort_by(fn {_p1, _p2, d} -> d end)
    |> Enum.reduce_while({%{}, %{}}, fn {p1, p2, _d}, {box_circuits, circuits} ->
      already_connected? =
        !is_nil(Map.get(box_circuits, p1)) and
          Map.get(box_circuits, p1) == Map.get(box_circuits, p2)

      cond do
        already_connected? ->
          IO.inspect(
            "skipping connection between connected boxes #{inspect(p1)} and #{inspect(p2)}"
          )

          {:cont, {box_circuits, circuits}}

        true ->
          IO.inspect("Connecting boxes #{inspect(p1)} and #{inspect(p2)}")
          circuit_1_id = Map.get(box_circuits, p1)
          circuit_2_id = Map.get(box_circuits, p2)
          circuit_1 = if circuit_1_id, do: Map.get(circuits, circuit_1_id), else: MapSet.new([p1])
          circuit_2 = if circuit_2_id, do: Map.get(circuits, circuit_2_id), else: MapSet.new([p2])

          circuit_id = circuit_1_id || circuit_2_id || System.unique_integer()
          circuit_boxes = MapSet.union(circuit_1, circuit_2)

          if MapSet.size(circuit_boxes) == point_count do
            {:halt, elem(p1, 0) * elem(p2, 0)}
          else
            circuits =
              circuits
              |> Map.delete(circuit_1_id)
              |> Map.delete(circuit_2_id)
              |> Map.put(circuit_id, circuit_boxes)

            box_circuits = Enum.reduce(circuit_boxes, box_circuits, &amp;Map.put(&amp;2, &amp;1, circuit_id))
            {:cont, {box_circuits, circuits}}
          end
      end
    end)
  end

  defp delta_squared({x1, y1, z1}, {x2, y2, z2}) do
    (x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2
  end
end
JunctionBoxes.part1(sample_input, 10)
JunctionBoxes.part1(real_input, 1000)
JunctionBoxes.part2(sample_input)
JunctionBoxes.part2(real_input)