Skip to content

Commit a56395f

Browse files
authored
Day 08 2025 (#302)
* Day 08 2025 * split out input parsing * remove duplicate code
1 parent f894da8 commit a56395f

File tree

5 files changed

+165
-0
lines changed

5 files changed

+165
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ as the value which passed the given day/phase combination
164164
* Day 13 Being able to solve the basic with shortest path while needing math for part two is always fun re-learning linear algebra. And refactoring always speeds things up.
165165
* Day 17 Another good example of how powerful Z3 can really be.
166166

167+
#### 2025
168+
* Day 05 My solution is O(n^3), but due to the size of the input runs ~20ms faster than the better solution of using sorted ranges (the sorting takes the time)
169+
167170
## Takeaways
168171

169172
* Setting up a Kotlin project with Dagger should have more examples than the multitude of Android examples out there
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package me.peckb.aoc._2025.calendar.day08
2+
3+
import javax.inject.Inject
4+
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
5+
import java.util.PriorityQueue
6+
import java.util.UUID
7+
import kotlin.collections.get
8+
import kotlin.math.sqrt
9+
import kotlin.math.pow
10+
11+
class Day08 @Inject constructor(
12+
private val generatorFactory: InputGeneratorFactory,
13+
) {
14+
fun partOne(filename: String) = generatorFactory.forFile(filename).readAs(::day08) { input ->
15+
val (_, distances) = createConnections(input)
16+
val circuits = connectBoxes(distances) { connections, _ ->
17+
// short circuit after we have made 1000 connections
18+
connections >= 1000
19+
}.first
20+
21+
circuits.map { it.value.boxes.size }.sortedByDescending { it }.take(3).fold(1) { acc, n -> acc * n }
22+
}
23+
24+
fun partTwo(filename: String) = generatorFactory.forFile(filename).readAs(::day08) { input ->
25+
val (boxes, distances) = createConnections(input)
26+
val lastConnection = connectBoxes(distances) { _, circuits ->
27+
// short circuit after we have a circuit that contains every box
28+
circuits[boxes.first().circuitId]?.boxes?.size == boxes.size
29+
}.second
30+
31+
lastConnection.fold(1L) { acc, n -> acc * n.x }
32+
}
33+
34+
private fun day08(line: String) = line.split(",")
35+
.map { it.toLong() }
36+
.let { (x, y, z) -> Box(x, y, z) }
37+
38+
private fun createConnections(input: Sequence<Box>): Pair<List<Box>, PriorityQueue<Distance>> {
39+
val boxes = input.toList()
40+
41+
val distances = PriorityQueue<Distance>()
42+
43+
boxes.forEachIndexed { index, box1 ->
44+
((index + 1)until boxes.size).forEach { box2Index ->
45+
distances.add(Distance(box1, boxes[box2Index]))
46+
}
47+
}
48+
49+
return boxes to distances
50+
}
51+
52+
private fun connectBoxes(distances: PriorityQueue<Distance>, shortCircuit: (Long, Map<String, Circuit>) -> Boolean) : Pair<MutableMap<String, Circuit>, MutableList<Box>> {
53+
var connectionsMade = 0L
54+
val circuits = mutableMapOf<String, Circuit>()
55+
val lastConnection = mutableListOf<Box>()
56+
57+
while(distances.isNotEmpty()) {
58+
connectionsMade++
59+
val distance = distances.poll()
60+
val b1 = distance.b1
61+
val b2 = distance.b2
62+
63+
if (b1.circuitId != null && b2.circuitId == b1.circuitId) {
64+
// if the two are already in the same circuit
65+
// do nothing!
66+
} else if (b1.circuitId == null && b2.circuitId == null) {
67+
// if neither are in a circuit, make a new circuit that contains them
68+
val circuit = Circuit(UUID.randomUUID().toString())
69+
b1.circuitId = circuit.id
70+
b2.circuitId = circuit.id
71+
circuit.add(b1)
72+
circuit.add(b2)
73+
circuits[circuit.id] = circuit
74+
} else if (b1.circuitId == null) {
75+
// b1 gets to join b2's circuit
76+
circuits[b2.circuitId]!!.add(b1)
77+
b1.circuitId = b2.circuitId
78+
} else if (b2.circuitId == null) {
79+
// b2 gets to join b1's circuit
80+
circuits[b1.circuitId]!!.add(b2)
81+
b2.circuitId = b1.circuitId
82+
} else {
83+
// we need to collapse the circuits!
84+
val firstCircuit = circuits[b1.circuitId]!!
85+
val secondCircuit = circuits[b2.circuitId]!!
86+
// we pick b2 to "lose" and join b1 - theoretically you would want to have the smaller circuit join the larger
87+
circuits.remove(b2.circuitId)
88+
secondCircuit.boxes.forEach { box ->
89+
box.circuitId = firstCircuit.id
90+
firstCircuit.add(box)
91+
}
92+
}
93+
94+
if (shortCircuit(connectionsMade, circuits)) {
95+
lastConnection.add(b1)
96+
lastConnection.add(b2)
97+
distances.clear()
98+
}
99+
}
100+
101+
return circuits to lastConnection
102+
}
103+
}
104+
105+
data class Box(val x: Long, val y: Long, val z: Long, var circuitId: String? = null) {
106+
override fun toString(): String = "($x, $y, $z)"
107+
}
108+
109+
data class Circuit(val id: String, val boxes: MutableList<Box> = mutableListOf()) {
110+
fun add(box: Box) = boxes.add(box)
111+
112+
override fun toString(): String = "size: ${boxes.size}"
113+
}
114+
115+
data class Distance(val b1: Box, val b2: Box, val distance: Double = euclideanDistance(b1, b2)) : Comparable<Distance> {
116+
override fun compareTo(other: Distance): Int {
117+
return this.distance.compareTo(other.distance)
118+
}
119+
}
120+
121+
fun euclideanDistance(b1: Box, b2: Box): Double {
122+
val deltaX = b2.x - b1.x
123+
val deltaY = b2.y - b1.y
124+
val deltaZ = b2.z - b1.z
125+
126+
return sqrt(deltaX.toDouble().pow(2) + deltaY.toDouble().pow(2) + deltaZ.toDouble().pow(2))
127+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## [Day 8: Playground](https://adventofcode.com/2025/day/8)

src/test/kotlin/me/peckb/aoc/_2025/TestDayComponent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import me.peckb.aoc._2025.calendar.day04.Day04Test
77
import me.peckb.aoc._2025.calendar.day05.Day05Test
88
import me.peckb.aoc._2025.calendar.day06.Day06Test
99
import me.peckb.aoc._2025.calendar.day07.Day07Test
10+
import me.peckb.aoc._2025.calendar.day08.Day08Test
1011
import javax.inject.Singleton
1112
import me.peckb.aoc.DayComponent
1213
import me.peckb.aoc.InputModule
@@ -22,4 +23,5 @@ internal interface TestDayComponent : DayComponent {
2223
fun inject(day05Test: Day05Test)
2324
fun inject(day06Test: Day06Test)
2425
fun inject(day07Test: Day07Test)
26+
fun inject(day08Test: Day08Test)
2527
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.peckb.aoc._2025.calendar.day08
2+
3+
import javax.inject.Inject
4+
5+
import me.peckb.aoc._2025.DaggerTestDayComponent
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
internal class Day08Test {
11+
@Inject
12+
lateinit var day08: Day08
13+
14+
@BeforeEach
15+
fun setup() {
16+
DaggerTestDayComponent.create().inject(this)
17+
}
18+
19+
@Test
20+
fun testDay08PartOne() {
21+
assertEquals(68112, day08.partOne(DAY_08))
22+
}
23+
24+
@Test
25+
fun testDay08PartTwo() {
26+
assertEquals(44543856, day08.partTwo(DAY_08))
27+
}
28+
29+
companion object {
30+
private const val DAY_08: String = "advent-of-code-input/2025/day08.input"
31+
}
32+
}

0 commit comments

Comments
 (0)