vkryukov
Advent of Code 2025 - Day 8
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
Most Liked
Aetherus
My code is almost the same as yours, except one difference: I added an ID (generated with make_ref()) to each MapSet.t() so that I don’t have to compare two whole sets, instead I just compare their ID.
defmodule AoC2025.Day08 do
def part1(points) do
distances = calc_distances(points, [])
initial_acc = Map.new(points, fn point ->
{
point, # key = {x, y, z}
{make_ref(), MapSet.new([point])} # value = {set_id, set}
}
end)
{distances, initial_acc}
|> Stream.iterate(fn {[{_, p1, p2} | distances], acc} ->
{id1, set1} = acc[p1]
{id2, set2} = acc[p2]
if id1 == id2 do
{distances, acc}
else
{_, merged_set} = union = {id1, MapSet.union(set1, set2)}
{distances, Enum.reduce(merged_set, acc, fn p, acc ->
Map.put(acc, p, union)
end)}
end
end)
|> Enum.at(1000)
|> elem(1)
|> Map.values()
|> Enum.uniq_by(&elem(&1, 0))
|> Enum.map(&elem(&1, 1))
|> Enum.map(&MapSet.size/1)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.product()
end
def part2(points) do
distances = calc_distances(points, [])
initial_acc = Map.new(points, fn point ->
{
point, # key = {x, y, z}
{make_ref(), MapSet.new([point])} # value = {set_id, set}
}
end)
{distances, initial_acc, nil, nil}
|> Stream.iterate(fn {[{_, p1, p2} | distances], acc, x1, x2} ->
{id1, set1} = acc[p1]
{id2, set2} = acc[p2]
if id1 == id2 do
{distances, acc, x1, x2}
else
{_, merged_set} = union = {id1, MapSet.union(set1, set2)}
{distances, Enum.reduce(merged_set, acc, fn p, acc ->
Map.put(acc, p, union)
end), elem(p1, 0), elem(p2, 0)}
end
end)
|> Enum.find(fn {_, acc, _, _} ->
acc
|> Map.values()
|> Enum.map(&elem(&1, 0))
|> Enum.uniq()
|> length()
|> Kernel.==(1)
end)
|> then(fn {_, _, x1, x2} ->
x1 * x2
end)
end
defp calc_distances([], acc) do
Enum.sort(acc)
end
defp calc_distances([{x1, y1, z1} = p1 | t], acc) do
acc =
for {x2, y2, z2} = p2 <- t, reduce: acc do
acc ->
distance = (x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2
[{distance, p1, p2} | acc]
end
calc_distances(t, acc)
end
end
2
hauleth
AFAIK - no, but I think that in most cases it will be negligible. I would need to check how exactly map equality is implemented.
2
dompdv
My solution, very late (I couldn’t find time today to work on it before 22:30);
Nothing new/fancy when I look at those already posted: pair distances are pre-computed and sorted, the main data structure is a map %{index of the circuit => MapSet of the box coordinates in the circuit}
defmodule AdventOfCode.Solution.Year2025.Day08 do
# Launch processing for part1 & 2
def part1(input), do: input |> do_connect(1000) |> mul_3_largest()
def part2(input), do: input |> do_connect(:full_connect) |> mul_xs()
# Post processing part 1
def mul_3_largest(circuits) do
circuits
|> Enum.map(fn {_, c} -> MapSet.size(c) end)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.reduce(1, &(&1 * &2))
end
# Post processing part 2
def mul_xs({[x1, _, _], [x2, _, _]}), do: x1 * x2
# The common algorithm : preparing the wiring process
def do_connect(input, stop_at) do
boxes_i = parse(input)
sorted_connects =
for({b1, i1} <- boxes_i, {b2, i2} <- boxes_i, i1 < i2, do: add_distance(b1, b2))
|> Enum.sort_by(&elem(&1, 2))
initial_circuits = for {b, i} <- boxes_i, into: %{}, do: {i, MapSet.new([b])}
wire(sorted_connects, initial_circuits, 0, stop_at, nil)
end
def add_distance(b1, b2),
do: {b1, b2, Enum.zip(b1, b2) |> Enum.map(fn {a, b} -> (a - b) * (a - b) end) |> Enum.sum()}
# Wiring process itself
# Stop if there is one circuit left
def wire(_, circuits, _n, _stop_at, last) when map_size(circuits) == 1, do: last
# Stop if we reach "stop_at" if stop_at is a number
def wire(_, circuits, n, stop_at, _last) when stop_at != :full_connect and n == stop_at,
do: circuits
def wire([{b1, b2, _} | rest], circuits, n, stop_at, _last) do
c_b1 = which_circuit(circuits, b1)
c_b2 = which_circuit(circuits, b2)
if c_b1 == c_b2 do
# They are in the same circuit => do nothing
wire(rest, circuits, n + 1, stop_at, {b1, b2})
else
# Different circuit, merge circuit c_b2 into c_b1
new_circuits =
circuits |> Map.delete(c_b2) |> Map.update(c_b1, nil, &MapSet.union(&1, circuits[c_b2]))
wire(rest, new_circuits, n + 1, stop_at, {b1, b2})
end
end
def which_circuit(circuits, b) do
# returns the index of the circuit containing the box b
Enum.reduce_while(circuits, nil, fn {i, c}, _acc ->
if b in c, do: {:halt, i}, else: {:cont, nil}
end)
end
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line |> String.split(",", trim: true) |> Enum.map(&String.to_integer/1)
end)
|> Enum.with_index()
end
end
2
Popular in Challenges
I said I was on a break, but I took a sneak peak and it looked fun so…
Part 1 completes in half a millisecond with a single pass of the ...
New
I found today a bit tedious: advent-of-code-2024/lib/advent_of_code2024/day15.ex at main · ibarakaiev/advent-of-code-2024 · GitHub.
New
Here is my solution for day 2 of Advent of Code:
https://github.com/bjorng/advent-of-code/blob/main/2024/day02/lib/day02.ex
New
Continuation of Advent of Code 2022:christmas_tree:, Day 1:
Day 2!
Leaderboard:
New
The second part of today’s puzzle is very misleading.
FYI, each of the ghosts has only one possible position that ends with a "Z" on its...
New
Anyone else think the prompt for this challenge is contradictory?
The rules for comparing packets include
If both values are lists, c...
New
Note: This topic is to talk about Day 9 of the Advent of Code 2019.
There is a private leaderboard for elixirforum members. You can join...
New
Hi, there :wave:
Today, I felt it was way more challenging! I went through part2 thanks to Agent based memoization (without memoization ...
New
Hello everyone!
This year is going to be shorter, but the difficulty will grow faster. Today I already feel that this is not standard “D...
New
Ok, that was a rough one today.
I haven’t found a way to improve the algorithm further. Part 1 runs in .5 seconds, Part 2 in ~ 5 minutes...
New
Other popular topics
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service.
Currently when I de...
New
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set?
Thanks.
New
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: )
Hello all, this is ...
New
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this:
...
New
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
Update:
How to use the Blogs & Podcasts section
You can post links to your blog posts or podcasts either in one of the Official Blog...
New
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









