Nice little problem. The right data structure is half the solution!
defmodule Y2025.Day08 do
def pairs(enum) do
l = enum |> Enum.with_index()
for {a, i} <- l, {b, j} <- l, i < j, do: {a, b}
end
def norm([x1, y1, z1], [x2, y2, z2]),
do: (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)
defmodule Circuits do
def new(enum), do: Enum.into(enum, %{}, fn x -> {x, MapSet.new([x])} end)
def fuze(circuits, c1, c2) do
s1 = circuits[c1]
s2 = circuits[c2]
if s1 == s2 do
circuits
else
s = MapSet.union(s1, s2)
s |> Enum.reduce(circuits, fn el, circuits -> Map.put(circuits, el, s) end)
end
end
def product_top_3_sizes(circuits) do
circuits
|> Enum.uniq_by(&elem(&1, 1))
|> Enum.map(fn {_, v} -> MapSet.size(v) end)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.product()
end
end
def parse(s), do: s |> String.split("\n") |> Enum.map(&parse_line/1) |> Circuits.new()
def parse_line(s), do: s |> String.trim() |> String.split(",") |> Enum.map(&String.to_integer/1)
def fuze_while(circuits, start, fun) do
Map.keys(circuits)
|> pairs()
|> Enum.map(fn {a, b} -> {a, b, norm(a, b)} end)
|> Enum.sort_by(&elem(&1, 2))
|> Enum.reduce_while(start, fun)
end
def part1(s, n) do
circuits = parse(s)
fuze_while(circuits, {circuits, 0}, fn {c1, c2, _}, {circuits, count} ->
if count == n do
{:halt, circuits |> Circuits.product_top_3_sizes()}
else
{:cont, {Circuits.fuze(circuits, c1, c2), count + 1}}
end
end)
end
def part2(s) do
circuits = parse(s)
n = map_size(circuits)
fuze_while(circuits, circuits, fn {c1, c2, _}, circuits ->
if MapSet.union(circuits[c1], circuits[c2]) |> MapSet.size() == n do
{:halt, List.first(c1) * List.first(c2)}
else
{:cont, Circuits.fuze(circuits, c1, c2)}
end
end)
end
end






















