|
| 1 | +package day08 |
| 2 | + |
| 3 | +import scala.util.boundary |
| 4 | +import scala.util.boundary.break |
| 5 | + |
| 6 | +import locations.Directory.currentDir |
| 7 | +import inputs.Input.loadFileSync |
| 8 | + |
| 9 | +def loadInput(): String = loadFileSync(s"$currentDir/../input/day08") |
| 10 | + |
| 11 | +class Point(val x: Long, val y: Long, val z: Long, var circuit: Int): |
| 12 | + def distance(p: Point): Long = |
| 13 | + (x - p.x) * (x - p.x) + (y - p.y) * (y - p.y) + (z - p.z) * (z - p.z) |
| 14 | + |
| 15 | +/** Parses comma-separated coordinates from the given `line` into a `Point` |
| 16 | + * with the given `circuit` ID. |
| 17 | + */ |
| 18 | +def point(line: String, circuit: Int): Point = |
| 19 | + val parts = line.split(",") |
| 20 | + Point(parts(0).toLong, parts(1).toLong, parts(2).toLong, circuit) |
| 21 | + |
| 22 | +/** Parses the input file at `path`, returning a sequence of `Point`s and all |
| 23 | + * unique pairs of points sorted by distance. |
| 24 | + */ |
| 25 | +def load(input: String): (Seq[Point], Seq[(Point, Point)]) = |
| 26 | + val lines = input.linesIterator.filter(_.nonEmpty) |
| 27 | + val points = lines.zipWithIndex.map(point).toSeq |
| 28 | + val pairsByDistance = points.pairs.toSeq.sortBy((p1, p2) => p1.distance(p2)) |
| 29 | + (points, pairsByDistance) |
| 30 | + |
| 31 | +/** Sets all points in `points` with circuit `c2` to circuit `c1`. */ |
| 32 | +def merge(c1: Int, c2: Int, points: Seq[Point]): Unit = |
| 33 | + for p <- points if p.circuit == c2 do p.circuit = c1 |
| 34 | + |
| 35 | +extension [T](self: Seq[T]) |
| 36 | + /** Generates all unique pairs (combinations of 2) from given the sequence. */ |
| 37 | + def pairs: Iterator[(T, T)] = |
| 38 | + self.combinations(2).map(pair => (pair(0), pair(1))) |
| 39 | + |
| 40 | +@main def part1: Unit = |
| 41 | + println(s"The solution is ${part1(loadInput())}") |
| 42 | + |
| 43 | +def part1(input: String): Int = |
| 44 | + val (points, pairsByDistance) = load(input) |
| 45 | + for (p1, p2) <- pairsByDistance.take(1000) if p1.circuit != p2.circuit do |
| 46 | + merge(p1.circuit, p2.circuit, points) |
| 47 | + val sizes = points.groupBy(_.circuit).values.map(_.size).toSeq.sortBy(-_) |
| 48 | + sizes.take(3).product |
| 49 | + |
| 50 | +@main def part2: Unit = |
| 51 | + println(s"The solution is ${part2(loadInput())}") |
| 52 | + |
| 53 | +def part2(input: String): Long = |
| 54 | + val (points, pairsByDistance) = load(input) |
| 55 | + var n = points.length |
| 56 | + boundary: |
| 57 | + for (p1, p2) <- pairsByDistance if p1.circuit != p2.circuit do |
| 58 | + merge(p1.circuit, p2.circuit, points) |
| 59 | + n -= 1 |
| 60 | + if n <= 1 then |
| 61 | + break(p1.x * p2.x) |
| 62 | + throw Exception("Should not reach here") |
0 commit comments