Spoiler alert
This is a full solution for Advent of Code 2025, day 8 puzzles with explanations.
If you want to try to solve it yourself first, head over to https://adventofcode.com/2025/day/8 and give it a go and then come back and compare notes.
You can find the full code at https://github.com/Hamatti/adventofcode-2025/blob/main/src/day_8.py
On the 8th day, we’ve reached a playground of sorts. When I read that we are at “a vast underground space which contains a giant playground”, the first image that popped into my head was the Red Light, Green Light game from Squid Game. But luckily there doesn’t seem to be any murderous giant dolls in this playground.
Instead, there’s a bunch of elves setting up junction boxes to power up decorations. Whew, that’s much better.
Read input
For today’s puzzle, I ended up modeling quite a class with a bunch of functionality. I did not write it all on one go before starting with part 1 but rather, little by little as I discovered more needs for new functionality.
Our input is a list of coordinates in 3D space, each marking a location of a junction box
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
To model this, I ended up with a JunctionBox:
from dataclasses import dataclass
@dataclass
class JunctionBox:
x: int
y: int
z: int
circuit: set[JunctionBox]
def distance(self, other: JunctionBox) -> JunctionBox:
"""Calculate Euclidian distance between two boxes"""
return sqrt((self.x - other.x)**2 + (self.y - other.y) ** 2 + (self.z - other.z) ** 2)
def connect(self, other: JunctionBox):
"""Connect two JunctionBoxes in a way where
every connection between nodes is shared."""
# Create a new circuit of self, other and all boxes connected to either
circuit = self.circuit | {other} | other.circuit | {self}
# Update circuit information to all
for box in circuit:
box.circuit = circuit
def __gt__(self, other: JunctionBox) -> bool:
return len(self) > len(other)
def __lt__(self, other: JunctionBox) -> bool:
return len(self) < len(other)
def __hash__(self):
return self.x * 1001 + self.y * 2002 + self.z * 3003
def __str__(self):
return f'({self.x}, {self.y}, {self.z}): {len(self.circuit)}'
def __len__(self):
return len(self.circuit)Each box knows its own position in the field and what other boxes it is connected to.
There’s a bunch of special methods (ones that start and end with double underscore), also known as “magic methods”:
__gt__ and __lt__ are for “greater than” and “less than” and they make a list of JunctionBoxes sortable. __hash__ allows us to store them in a set or use as dict keys. __str__ makes it easier to debug as we’re not printing out the entire circuit every time.
And __len__ is used later to compare circuit sizes. Whether the circuit data should be modeled in this JunctionBox is debatable. In a perfect world, I probably wouldn’t but today’s puzzle was hard and I couldn’t figure out a good way to maintain circuits in other ways so we’re going with this.
To map our input into these objects, we have a mapper function:
def mapper(line: str) -> JunctionBox:
x, y, z = [int(coord) for coord in line.split(',')]
return JunctionBox(x=x, y=y, z=z, circuit=set())Part 1
In the first part, we need to connect junction boxes so that we start with the two that are closest to each other and then move on until we’ve connected 1000 boxes. Finally, we need to find three largest circuits and multiply their sizes to get the result.
from itertools import combinations
@dataclass
class JunctionBoxPair:
one: JunctionBox
other: JunctionBox
def __gt__(self, other: JunctionBoxPair) -> bool:
return self.one.distance(self.other) > other.one.distance(other.other)
def __lt__(self, other: JunctionBoxPair) -> bool:
return self.one.distance(self.other) < other.one.distance(other.other)
def part_1() -> int:
boxes = read_input(8, mapper)
MAX_CONNECTIONS = 1000
all_pairs = sorted([JunctionBoxPair(a, b) for a, b in combinations(boxes, 2)])
to_connect = all_pairs[:MAX_CONNECTIONS]
for pair in to_connect:
pair.one.connect(pair.other)We start by creating the 1000 connections.
To do so, we use itertools.combinations to create all possible pairings and then sort them by their distance. I modeled a pair of boxes as a JunctionBoxPair so I could make them sortable by distance.
We then loop the first 1000 of these pairs in order of closest to furthest and connect them.
biggest_circuits = sorted(boxes, reverse=True)
prod = 1
circuits_seen = []
unique_circuits_counted = 0
for box in biggest_circuits:
if box.circuit in circuits_seen:
continue
circuits_seen.append(box.circuit)
prod *= len(box)
unique_circuits_counted += 1
if unique_circuits_counted == 3:
break
return prodThen, I sort the junction boxes based on the size of the circuit they belong to. As I mentioned earlier, this is not a great modeling because there’s no good reason to say that a size of a junction box is the size of its circuit.
I then go through these circuits and since one circuit has multiple boxes, we need to make sure we don’t calculate them twice. The weird modeling makes this unnecessarily complicated to follow.
Once we’ve found three unique circuits, we multiple their sizes together for the result.
This is also not a very efficient algorithm as it takes about 2 seconds to run on my computer. There’s a lot of looping and sorting here.
Most of my thinking process for this solution was done away from computer as I was traveling by buses across the city in the morning. Sometimes it helps to disconnect from the code and try to reason with the problem.
Part 2
While the first part was a struggle for me to figure out, the second part was basically trivial. Maybe I got lucky with my modeling in the first part but it was weird to get this right on the first try and with so much fewer code.
In this part, the goal was to find the pair of boxes that when connected, lead to all boxes being in the same circuit.
Here, I also create a sorted list of all pairings and then connect them until a box’s circuit is as large as the amount of boxes all together. Then we multiply the x coordinates of both boxes for the score.
This takes roughly 3 seconds to run so it’s not very optimised but it’s good enough for me for now.
def part_2() -> int:
boxes = read_input(8, mapper)
all_pairs = sorted([JunctionBoxPair(a, b) for a, b in combinations(boxes, 2)])
for pair in all_pairs:
pair.one.connect(pair.other)
if len(pair.one) == len(boxes):
break
return pair.one.x * pair.other.xGet in touch
If you want to comment on these solutions, I share them in Mastodon where you can look for the post for the right date or you can start a discussion over email with juhamattisantala@gmail.com.
Follow via RSS
I have created a custom RSS feed for these solutions so you can follow along and not miss any of them!