Skip to content

Commit 7ef08f6

Browse files
committed
Optimise adventofcode 2025 Day 9: 3mins to 3secs
Bottleneck it turns out was NOT the area calcs, but the building of `tiledRanges`. Previously O(rows * tiles) now O(tiles). Parallelism improves it to 1sec only. Make sure to measure the code and discover the bottleneck! GitOrigin-RevId: 7ea818c51b4f481ac65c7bbd9638bfd25a24fcc3
1 parent 15cf6e7 commit 7ef08f6

File tree

4 files changed

+50
-76
lines changed

4 files changed

+50
-76
lines changed

advent-of-code/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ tasks.withType<ScalaCompile> {
1313
dependencies {
1414
implementation(libs.scala.library)
1515
implementation(libs.scala.parallel.collections)
16+
implementation(libs.kotlin.coroutines)
1617
}
1718

1819
// TODO disabling on Kotlin/Scala atm... too many false positives

advent-of-code/src/main/kotlin/com/willmolloy/adventofcode/_2025/Day9.kt

Lines changed: 36 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ package com.willmolloy.adventofcode._2025
22

33
import com.willmolloy.adventofcode.common.Day
44
import com.willmolloy.adventofcode.common.Input
5-
import com.willmolloy.adventofcode.common.extensions.debug
5+
import com.willmolloy.adventofcode.common.extensions.pairs
66
import com.willmolloy.adventofcode.common.extensions.size
77
import com.willmolloy.adventofcode.common.grid.Point
88
import kotlin.collections.lastIndex
9-
import kotlin.math.abs
109
import kotlin.math.max
1110
import kotlin.math.min
1211

@@ -16,106 +15,67 @@ object Day9 : Day(2025, 9) {
1615
override fun part1(input: Input): Any {
1716
val redTiles = getRedTiles(input)
1817

19-
var max = 0L
20-
21-
for ((i, p1) in redTiles.withIndex()) {
22-
for (p2 in redTiles.drop(i + 1)) {
23-
val area = (abs(p2.y - p1.y) + 1) * (abs(p2.x - p1.x) + 1)
24-
max = max(max, area)
25-
}
26-
}
27-
28-
return max
29-
}
30-
31-
private fun getRedTiles(input: Input): List<Point> =
32-
input.readLines().map {
33-
val split = it.split(",")
34-
Point(split[0], split[1])
35-
}
36-
37-
private fun getLoopOfGreenTiles(redTiles: List<Point>): List<Point> {
38-
val greenTiles = mutableListOf<Point>()
39-
40-
for (i in 0 until redTiles.size) {
41-
val point = redTiles[i]
42-
val adjPoint = if (i < redTiles.lastIndex) redTiles[i + 1] else redTiles[0]
43-
// "Tiles that are adjacent will always be on either the same row or the same column."
44-
if (point.x == adjPoint.x) {
45-
// same col
46-
for (y in min(point.y, adjPoint.y)..max(point.y, adjPoint.y)) {
47-
greenTiles.add(Point(point.x, y))
48-
}
49-
} else {
50-
// same row
51-
for (x in min(point.x, adjPoint.x)..max(point.x, adjPoint.x)) {
52-
greenTiles.add(Point(x, point.y))
53-
}
54-
}
18+
fun area(p1: Point, p2: Point): Long {
19+
val cols = min(p1.x, p2.x)..max(p1.x, p2.x)
20+
val rows = min(p1.y, p2.y)..max(p1.y, p2.y)
21+
return cols.size * rows.size
5522
}
5623

57-
return greenTiles
24+
return redTiles.pairs().maxOf { area(it.first, it.second) }
5825
}
5926

60-
override fun part2(input: Input): Any {
27+
override fun part2(input: Input): Long {
6128
val redTiles = getRedTiles(input)
62-
val greenTiles = getLoopOfGreenTiles(redTiles)
29+
val greenTiles = getGreenTilesLoop(redTiles)
6330
val redGreenTiles = redTiles.union(greenTiles)
6431

6532
// "In addition, all the tiles inside this loop of red and green tiles are also green."
66-
val rowStart = redGreenTiles.minOf { it.y }
67-
val rowEnd = redGreenTiles.maxOf { it.y }
68-
println("ROWS = $rowStart..$rowEnd")
69-
7033
// ROW -> COL RANGE
71-
val tiledRanges = mutableMapOf<Long, LongRange>()
72-
73-
for (row in rowStart..rowEnd) {
74-
val tilesInRow = redGreenTiles.filter { it.y == row }
75-
if (tilesInRow.isEmpty()) {
76-
continue
77-
}
78-
79-
val colStart = tilesInRow.minOf { it.x }
80-
val colEnd = tilesInRow.maxOf { it.x }
81-
82-
tiledRanges[row] = colStart..colEnd
83-
}
84-
85-
tiledRanges.debug()
34+
val tiledRanges =
35+
redGreenTiles
36+
.groupBy { it.y }
37+
.mapValues { (_, tiles) -> tiles.minOf { it.x }..tiles.maxOf { it.x } }
8638

8739
fun area(p1: Point, p2: Point): Long {
88-
// no need to filter for topLeft/bottomRight points because all the points in the rectangle
89-
// are checked that they're tiled - so just force topLeft/bottomRight:
9040
val cols = min(p1.x, p2.x)..max(p1.x, p2.x)
9141
val rows = min(p1.y, p2.y)..max(p1.y, p2.y)
9242

9343
for (row in rows) {
9444
val tiledRange = tiledRanges[row] ?: return 0L
95-
if (tiledRange.start > cols.start) {
96-
return 0L
97-
}
98-
if (tiledRange.endInclusive < cols.endInclusive) {
45+
if (tiledRange.start > cols.start || tiledRange.endInclusive < cols.endInclusive) {
9946
return 0L
10047
}
10148
}
10249

10350
return cols.size * rows.size
10451
}
10552

106-
// "The rectangle you choose still must have red tiles in opposite corners"
107-
var max = 0L
53+
return redTiles.pairs().maxOf { area(it.first, it.second) }
54+
}
10855

109-
for ((i, p1) in redTiles.withIndex()) {
110-
for (p2 in redTiles.drop(i + 1)) {
111-
val area = area(p1, p2)
112-
if (area > max) {
113-
max = area
114-
println("NEW MAX: $p1 and $p2 = $area")
56+
private fun getRedTiles(input: Input): List<Point> =
57+
input.readLines().map {
58+
val split = it.split(",")
59+
Point(split[0], split[1])
60+
}
61+
62+
private fun getGreenTilesLoop(redTiles: List<Point>): List<Point> = buildList {
63+
for (i in 0 until redTiles.size) {
64+
val point = redTiles[i]
65+
val adjPoint = if (i < redTiles.lastIndex) redTiles[i + 1] else redTiles[0]
66+
67+
// "Tiles that are adjacent will always be on either the same row or the same column."
68+
if (point.x == adjPoint.x) {
69+
// same col
70+
for (y in min(point.y, adjPoint.y)..max(point.y, adjPoint.y)) {
71+
add(Point(point.x, y))
72+
}
73+
} else {
74+
// same row
75+
for (x in min(point.x, adjPoint.x)..max(point.x, adjPoint.x)) {
76+
add(Point(x, point.y))
11577
}
11678
}
11779
}
118-
119-
return max
12080
}
12181
}

advent-of-code/src/main/kotlin/com/willmolloy/adventofcode/common/extensions/Collections.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ fun <T> List<List<T>>.transpose(): List<List<T>> {
1616
return transpose as List<List<T>>
1717
}
1818

19+
fun <T> Iterable<T>.pairs(): List<Pair<T, T>> {
20+
val list = this.toList()
21+
return buildList {
22+
for (i in 0 until list.size) {
23+
for (j in i + 1 until list.size) {
24+
add(list[i] to list[j])
25+
}
26+
}
27+
}
28+
}
29+
1930
fun Iterable<Int>.product() = reduce { a, b -> a * b }
2031

2132
fun Iterable<Long>.product() = reduce { a, b -> a * b }

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ guava-testlib = { module = "com.google.guava:guava-testlib", version.ref = "guav
3434
# scala
3535
scala-library = { module = "org.scala-lang:scala3-library_3", version.ref = "scala" }
3636
scala-parallel-collections = { module = "org.scala-lang.modules:scala-parallel-collections_3", version = "1.2.0" }
37+
# kotlin
38+
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.1" }

0 commit comments

Comments
 (0)