Skip to content

Commit 72ed088

Browse files
committed
Add intersection solution to 2025 day 9 part 2
1 parent 744aa6c commit 72ed088

File tree

2 files changed

+61
-15
lines changed

2 files changed

+61
-15
lines changed

src/main/scala/eu/sim642/adventofcode2025/Day9.scala

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ object Day9 {
3131
_ => true
3232
}
3333

34+
trait Part2Solution extends Part
35+
3436
// Copied & modified from 2018 day 11
3537
class ArrayPartialSumGrid(f: Pos => Int, box: Box) extends SumGrid {
3638
val Box(min, max) = box
@@ -56,7 +58,11 @@ object Day9 {
5658
}
5759
}
5860

59-
object Part2 extends Part {
61+
/**
62+
* Solution, which compresses the grid to 2-unit distances between occurring coordinates
63+
* and checks validity by counting outside cells (in the compressed grid) using a [[SumGrid]].
64+
*/
65+
object CompressGridPart2Solution extends Part2Solution {
6066
def makeCompressPos(redTiles: Seq[Pos]): Pos => Pos = {
6167
val xs = redTiles.map(_.x).distinct.sorted
6268
val ys = redTiles.map(_.y).distinct.sorted
@@ -105,9 +111,31 @@ object Day9 {
105111
}
106112
}
107113

114+
/**
115+
* Solution, which checks validity geometrically using axis-aligned line-box intersection.
116+
*/
117+
object IntersectionPart2Solution extends Part2Solution {
118+
override def makeIsValid(redTiles: Seq[Pos]): Box => Boolean = {
119+
val lines =
120+
(redTiles lazyZip redTiles.rotateLeft(1))
121+
.toSeq
122+
.map((p1, p2) => Box.bounding(Seq(p1, p2))) // convert line to box of width/height 1
123+
124+
def isValid(box: Box): Boolean = {
125+
// construct box interior, otherwise box intersects with its own edges
126+
val innerMin = box.min + Pos(1, 1)
127+
val innerMax = box.max - Pos(1, 1)
128+
//innerMin <= innerMax &&
129+
!(innerMin <= innerMax) || // box has no/empty interior // TODO: this is wrong for line-line intersections
130+
!lines.exists(_.intersect(Box(innerMin, innerMax)).nonEmpty) // interior doesn't intersect any line
131+
}
132+
133+
isValid
134+
}
135+
}
136+
108137
// TODO: line of sight in all directions solution (single iteration over corners, not all pairs!)
109138
// TODO: glguy's solution
110-
// TODO: polygon-based solution?
111139

112140
def parseRedTile(s: String): Pos = s match {
113141
case s"$x,$y" => Pos(x.toInt, y.toInt)
@@ -119,6 +147,6 @@ object Day9 {
119147

120148
def main(args: Array[String]): Unit = {
121149
println(Part1.largestArea(parseRedTiles(input)))
122-
println(Part2.largestArea(parseRedTiles(input)))
150+
println(CompressGridPart2Solution.largestArea(parseRedTiles(input)))
123151
}
124152
}
Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package eu.sim642.adventofcode2025
22

3-
import Day9._
3+
import Day9.*
4+
import Day9Test.*
5+
import org.scalatest.Suites
46
import org.scalatest.funsuite.AnyFunSuite
57

6-
class Day9Test extends AnyFunSuite {
8+
class Day9Test extends Suites(
9+
new Part1Test,
10+
new CompressGridPart2SolutionTest,
11+
new IntersectionPart2SolutionTest,
12+
)
13+
14+
object Day9Test {
715

816
val exampleInput =
917
"""7,1
@@ -15,19 +23,29 @@ class Day9Test extends AnyFunSuite {
1523
|2,3
1624
|7,3""".stripMargin
1725

18-
test("Part 1 examples") {
19-
assert(Part1.largestArea(parseRedTiles(exampleInput)) == 50)
20-
}
26+
class Part1Test extends AnyFunSuite {
2127

22-
test("Part 1 input answer") {
23-
assert(Part1.largestArea(parseRedTiles(input)) == 4729332959L)
24-
}
28+
test("Part 1 examples") {
29+
assert(Part1.largestArea(parseRedTiles(exampleInput)) == 50)
30+
}
2531

26-
test("Part 2 examples") {
27-
assert(Part2.largestArea(parseRedTiles(exampleInput)) == 24)
32+
test("Part 1 input answer") {
33+
assert(Part1.largestArea(parseRedTiles(input)) == 4729332959L)
34+
}
2835
}
2936

30-
test("Part 2 input answer") {
31-
assert(Part2.largestArea(parseRedTiles(input)) == 1474477524L)
37+
abstract class Part2SolutionTest(part2Solution: Part2Solution) extends AnyFunSuite {
38+
39+
test("Part 2 examples") {
40+
assert(part2Solution.largestArea(parseRedTiles(exampleInput)) == 24)
41+
}
42+
43+
test("Part 2 input answer") {
44+
assert(part2Solution.largestArea(parseRedTiles(input)) == 1474477524L)
45+
}
3246
}
47+
48+
class CompressGridPart2SolutionTest extends Part2SolutionTest(CompressGridPart2Solution)
49+
50+
class IntersectionPart2SolutionTest extends Part2SolutionTest(IntersectionPart2Solution)
3351
}

0 commit comments

Comments
 (0)