Skip to content

Commit 6571b02

Browse files
committed
Optimize naive solution in 2025 day 12
Stop early based on similar sanity check heuristic. Also deduplicate orientations.
1 parent af3bd5b commit 6571b02

File tree

2 files changed

+50
-76
lines changed

2 files changed

+50
-76
lines changed

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

Lines changed: 45 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -23,89 +23,63 @@ object Day12 {
2323
}
2424

2525
object NaiveSolution extends Solution {
26-
/*def fits(shapes: Seq[Shape])(region: Region): Boolean = {
27-
28-
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int]): Boolean = {
29-
val shapeI = shapeCounts.indexWhere(_ > 0)
30-
if (shapeI < 0)
31-
true // nothing more to fit
32-
else {
33-
val newShapeCounts = shapeCounts.updated(shapeI, shapeCounts(shapeI) - 1)
34-
val shape = shapes(shapeI)
35-
(for {
36-
shapeOrientation <- shape.orientations.iterator
37-
shapeOrientationSize = Pos(shapeOrientation(0).size, shapeOrientation.size)
38-
shapeOrientationBox = Box(Pos.zero, region.size - shapeOrientationSize)
39-
shapeOrientationBox2 = Box(Pos.zero, shapeOrientationSize - Pos(1, 1))
40-
pos <- shapeOrientationBox.iterator
41-
if shapeOrientationBox2.iterator.forall(p => !(shapeOrientation(p) && grid(pos + p)))
42-
newGrid = shapeOrientationBox2.iterator.foldLeft(grid)((newGrid, p) => newGrid.updatedGrid(pos + p, true))
43-
} yield helper(newGrid, newShapeCounts)).exists(identity)
44-
}
45-
}
46-
47-
val initialGrid = Vector.fill(region.size.y, region.size.x)(false)
48-
helper(initialGrid, region.shapeCounts)
49-
}*/
50-
51-
// probably broken: skips over poss when finding place for a shape
5226
override def fits(shapes: Seq[Shape])(region: Region): Boolean = {
5327
val shapeOrientationBox = Box(Pos.zero, region.size - Pos(3, 3)) // assuming all shappes of same size
5428

55-
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int], poss: List[Pos]): Boolean = {
29+
val shapeOrientations = shapes.map(_.orientations.toSet)
30+
31+
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int], poss: List[Pos], depth: Int): Boolean = {
32+
val indent = " " * depth
33+
//println(s"$indent: $shapeCounts")
34+
//for (row <- grid) {
35+
// print(indent)
36+
// for (cell <- row)
37+
// print(if (cell) '#' else '.')
38+
// println()
39+
//}
5640
val shapeI = shapeCounts.indexWhere(_ > 0)
5741
if (shapeI < 0)
5842
true // nothing more to fit
5943
else {
60-
val newShapeCounts = shapeCounts.updated(shapeI, shapeCounts(shapeI) - 1)
61-
val shape = shapes(shapeI)
62-
(for {
63-
shapeOrientation <- shape.orientations.iterator
64-
shapeOrientationSize = Pos(shapeOrientation(0).size, shapeOrientation.size)
65-
shapeOrientationBox2 = Box(Pos.zero, shapeOrientationSize - Pos(1, 1))
66-
case pos :: newPoss <- poss.tails
67-
if shapeOrientationBox2.iterator.forall(p => !(shapeOrientation(p) && grid(pos + p)))
68-
newGrid = shapeOrientationBox2.iterator.foldLeft(grid)((newGrid, p) => newGrid.updatedGrid(pos + p, shapeOrientation(p)))
69-
} yield helper(newGrid, newShapeCounts, newPoss)).exists(identity)
44+
//val newShapeCounts = shapeCounts.updated(shapeI, shapeCounts(shapeI) - 1)
45+
//val shape = shapes(shapeI)
46+
if (poss.isEmpty)
47+
false
48+
else {
49+
val pos = poss.head
50+
//print(s"$indent $pos")
51+
val todo = region.size.x * region.size.y - pos.x * region.size.y - pos.y
52+
val areaLowerBound = // only counting the #-s in shapes
53+
shapes
54+
.map(_.countGrid(identity))
55+
.lazyZip(shapeCounts)
56+
.map(_ * _)
57+
.sum
58+
if (areaLowerBound > todo) {
59+
//println("bounded")
60+
false
61+
} else {
62+
//println()
63+
val newPoss = poss.tail
64+
(for {
65+
((shapeO, count), shapeI) <- shapeOrientations.lazyZip(shapeCounts).zipWithIndex
66+
if count > 0
67+
newShapeCounts = shapeCounts.updated(shapeI, count - 1)
68+
shapeOrientation <- shapeO.iterator
69+
shapeOrientationSize = Pos(shapeOrientation(0).size, shapeOrientation.size)
70+
shapeOrientationBox2 = Box(Pos.zero, shapeOrientationSize - Pos(1, 1))
71+
if shapeOrientationBox2.iterator.forall(p => !(shapeOrientation(p) && grid(pos + p)))
72+
newGrid = shapeOrientationBox2.iterator.foldLeft(grid)((newGrid, p) => newGrid.updatedGrid(pos + p, newGrid(pos + p) || shapeOrientation(p)))
73+
} yield helper(newGrid, newShapeCounts, newPoss, depth + 1)).exists(identity) ||
74+
!Box(Pos.zero, Pos(2, 2)).iterator.forall(p => !grid(pos + p)) && helper(grid, shapeCounts, newPoss, depth + 1) // or don't place anything here
75+
}
76+
}
7077
}
7178
}
7279

7380
val initialGrid = Vector.fill(region.size.y, region.size.x)(false)
74-
helper(initialGrid, region.shapeCounts, shapeOrientationBox.iterator.toList)
75-
}
76-
77-
/*def fits(shapes: Seq[Shape])(region: Region): Boolean = {
78-
val shapeOrientationBox = Box(Pos.zero, region.size - Pos(3, 3)) // assuming all shappes of same size
79-
80-
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int], poss: List[Pos]): Boolean = {
81-
val shapeI = shapeCounts.indexWhere(_ > 0)
82-
if (shapeI < 0)
83-
true // nothing more to fit
84-
else {
85-
//val newShapeCounts = shapeCounts.updated(shapeI, shapeCounts(shapeI) - 1)
86-
//val shape = shapes(shapeI)
87-
if (poss.isEmpty)
88-
false
89-
else {
90-
val pos = poss.head
91-
val newPoss = poss.tail
92-
(for {
93-
((shape, count), shapeI) <- shapes.lazyZip(shapeCounts).zipWithIndex
94-
if count > 0
95-
newShapeCounts = shapeCounts.updated(shapeI, count - 1)
96-
shapeOrientation <- shape.orientations.iterator
97-
shapeOrientationSize = Pos(shapeOrientation(0).size, shapeOrientation.size)
98-
shapeOrientationBox2 = Box(Pos.zero, shapeOrientationSize - Pos(1, 1))
99-
if shapeOrientationBox2.iterator.forall(p => !(shapeOrientation(p) && grid(pos + p)))
100-
newGrid = shapeOrientationBox2.iterator.foldLeft(grid)((newGrid, p) => newGrid.updatedGrid(pos + p, shapeOrientation(p)))
101-
} yield helper(newGrid, newShapeCounts, newPoss)).exists(identity) || helper(grid, shapeCounts, newPoss) // or don't place anything here
102-
}
103-
}
81+
helper(initialGrid, region.shapeCounts, shapeOrientationBox.iterator.toList, 0)
10482
}
105-
106-
val initialGrid = Vector.fill(region.size.y, region.size.x)(false)
107-
helper(initialGrid, region.shapeCounts, shapeOrientationBox.iterator.toList)
108-
}*/
10983
}
11084

11185
object SanitySolution extends Solution {

src/test/scala/eu/sim642/adventofcode2025/Day12Test.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ class Day12Test extends AnyFunSuite {
4040
|12x5: 1 0 1 0 2 2
4141
|12x5: 1 0 1 0 3 2""".stripMargin
4242

43-
ignore("Part 1 examples") { // TODO: optimize
43+
test("Part 1 examples") {
4444
import NaiveSolution._ // doesn't work with cheat solution
45-
//val input1 = parseInput(exampleInput)
46-
//assert(fits(input1.shapes)(input1.regions(0)))
47-
//assert(fits(input1.shapes)(input1.regions(1)))
48-
//assert(!fits(input1.shapes)(input1.regions(2)))
45+
val input1 = parseInput(exampleInput)
46+
assert(fits(input1.shapes)(input1.regions(0)))
47+
assert(fits(input1.shapes)(input1.regions(1)))
48+
assert(!fits(input1.shapes)(input1.regions(2)))
4949
assert(countFits(parseInput(exampleInput)) == 2)
5050
}
5151

0 commit comments

Comments
 (0)