From 786241cfaa72dd533bd20a0db2a89b31f50af649 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 8 Dec 2025 13:34:45 +0100 Subject: [PATCH] Add article for day 8 --- docs/2025/puzzles/day08.md | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/docs/2025/puzzles/day08.md b/docs/2025/puzzles/day08.md index 494818045..c8539a1e8 100644 --- a/docs/2025/puzzles/day08.md +++ b/docs/2025/puzzles/day08.md @@ -2,10 +2,123 @@ import Solver from "../../../../../website/src/components/Solver.js" # Day 8: Playground +by [@mbovel](https://github.com/mbovel) + ## Puzzle description https://adventofcode.com/2025/day/8 +## Data Model + +To solve this puzzle, we use a class to represent junction boxes: + +```scala +/** A junction box in 3D space with an associated circuit ID. */ +class Box(val x: Long, val y: Long, val z: Long, var circuit: Int): + def distanceSquare(other: Box): Long = + (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y) + (z - other.z) * (z - other.z) +``` + +Each `Box` has: +- Three coordinates (`x`, `y`, `z`) representing its position in 3D space +- A mutable `circuit` field to track which circuit the box belongs to (each circuit is identified by a distinct integer) +- A `distanceSquare` method that computes the squared Euclidean distance to another box (we use squared distance to avoid computing square roots, since we only need to compare distances) + +## Data Loading + +The following functions parse the input into a sequence of boxes and compute all unique pairs sorted by distance: + +```scala +/** Parses comma-separated coordinates from the given `line` into a `Box` + * with the given `circuit` ID. + */ +def parseBox(line: String, circuit: Int): Box = + val parts = line.split(",") + Box(parts(0).toLong, parts(1).toLong, parts(2).toLong, circuit) + +/** Parses the input, returning a sequence of `Box`es and all unique pairs + * of boxes sorted by distance. + */ +def load(input: String): (Seq[Box], Seq[(Box, Box)]) = + val lines = input.linesIterator.filter(_.nonEmpty) + val boxes = lines.zipWithIndex.map(parseBox).toSeq + val pairsByDistance = boxes.pairs.toSeq.sortBy((b1, b2) => b1.distanceSquare(b2)) + (boxes, pairsByDistance) +``` + +The `pairs` extension method generates all unique pairs from a sequence: + +```scala +extension [T](self: Seq[T]) + /** Generates all unique pairs (combinations of 2) from the sequence. */ + def pairs: Iterator[(T, T)] = + self.combinations(2).map(pair => (pair(0), pair(1))) +``` + +## Part 1 + +For Part 1, we process the 1000 closest pairs of boxes and merge their circuits. The algorithm iterates through pairs in order of increasing distance; when two boxes belong to different circuits, we merge them into one. Finally, we find the three largest circuits and return the product of their sizes. + +```scala +def part1(input: String): Int = + val (boxes, pairsByDistance) = load(input) + for (b1, b2) <- pairsByDistance.take(1000) if b1.circuit != b2.circuit do + merge(b1.circuit, b2.circuit, boxes) + val sizes = boxes.groupBy(_.circuit).values.map(_.size).toSeq.sortBy(-_) + sizes.take(3).product +``` + +The `merge` function updates all boxes in one circuit to belong to another: + +```scala +/** Sets all boxes with circuit `c2` to circuit `c1`. */ +def merge(c1: Int, c2: Int, boxes: Seq[Box]): Unit = + for b <- boxes if b.circuit == c2 do b.circuit = c1 +``` + + +## Part 2 + +For Part 2, we continue merging circuits until only one remains. We track the number of distinct circuits and return the product of the x-coordinates of the two boxes in the final merge. + +```scala +def part2(input: String): Long = + val (boxes, pairsByDistance) = load(input) + var n = boxes.length + boundary: + for (b1, b2) <- pairsByDistance if b1.circuit != b2.circuit do + merge(b1.circuit, b2.circuit, boxes) + n -= 1 + if n <= 1 then + break(b1.x * b2.x) + throw Exception("Should not reach here") +``` + +[`boundary` and `break`](https://www.scala-lang.org/api/3.x/scala/util/boundary$.html) provide a way to exit the loop early and return a value when only one circuit remains. + +## Potential Optimizations + +On my machine, both parts run in under 2 seconds, which is acceptable for this puzzle. However, there are potential optimizations: + +- **Finding the k closest pairs**: Computing all pairs and sorting them has O(n² log n) complexity. A spatial data structure like a [*k*-d tree](https://en.wikipedia.org/wiki/K-d_tree) could find nearest neighbors more efficiently. +- **Merging circuits**: The `merge` function iterates over all boxes to update circuit IDs. A [union-find](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) data structure would track connected components more efficiently. + +## Final Code + +See the complete code on [GitHub](https://github.com/scalacenter/scala-advent-of-code/blob/main/2025/src/day08.scala). + +## Run it in the browser + +Thanks to the [Scala.js](https://www.scala-js.org/) build, you can also experiment with this code directly in the browser. + +### Part 1 + + + +### Part 2 + + + ## Solutions from the community Share your solution to the Scala community by editing this page.