Skip to content

Commit af3bd5b

Browse files
committed
Clean up 2025 day 12
1 parent 49cefcc commit af3bd5b

File tree

2 files changed

+61
-42
lines changed

2 files changed

+61
-42
lines changed

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

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ object Day12 {
1515

1616
case class Input(shapes: Seq[Shape], regions: Seq[Region])
1717

18-
/*def fits(shapes: Seq[Shape])(region: Region): Boolean = {
18+
trait Solution {
19+
def fits(shapes: Seq[Shape])(region: Region): Boolean
20+
21+
def countFits(input: Input): Int =
22+
input.regions.count(fits(input.shapes))
23+
}
24+
25+
object NaiveSolution extends Solution {
26+
/*def fits(shapes: Seq[Shape])(region: Region): Boolean = {
1927
2028
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int]): Boolean = {
2129
val shapeI = shapeCounts.indexWhere(_ > 0)
@@ -40,35 +48,33 @@ object Day12 {
4048
helper(initialGrid, region.shapeCounts)
4149
}*/
4250

51+
// probably broken: skips over poss when finding place for a shape
52+
override def fits(shapes: Seq[Shape])(region: Region): Boolean = {
53+
val shapeOrientationBox = Box(Pos.zero, region.size - Pos(3, 3)) // assuming all shappes of same size
4354

44-
/*// probably broken: skips over poss when finding place for a shape
45-
def fits(shapes: Seq[Shape])(region: Region): Boolean = {
46-
val shapeOrientationBox = Box(Pos.zero, region.size - Pos(3, 3)) // assuming all shappes of same size
47-
48-
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int], poss: List[Pos]): Boolean = {
49-
val shapeI = shapeCounts.indexWhere(_ > 0)
50-
if (shapeI < 0)
51-
true // nothing more to fit
52-
else {
53-
val newShapeCounts = shapeCounts.updated(shapeI, shapeCounts(shapeI) - 1)
54-
val shape = shapes(shapeI)
55-
(for {
56-
shapeOrientation <- shape.orientations.iterator
57-
shapeOrientationSize = Pos(shapeOrientation(0).size, shapeOrientation.size)
58-
shapeOrientationBox2 = Box(Pos.zero, shapeOrientationSize - Pos(1, 1))
59-
case pos :: newPoss <- poss.tails
60-
if shapeOrientationBox2.iterator.forall(p => !(shapeOrientation(p) && grid(pos + p)))
61-
newGrid = shapeOrientationBox2.iterator.foldLeft(grid)((newGrid, p) => newGrid.updatedGrid(pos + p, shapeOrientation(p)))
62-
} yield helper(newGrid, newShapeCounts, newPoss)).exists(identity)
55+
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int], poss: List[Pos]): Boolean = {
56+
val shapeI = shapeCounts.indexWhere(_ > 0)
57+
if (shapeI < 0)
58+
true // nothing more to fit
59+
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)
70+
}
6371
}
64-
}
65-
66-
val initialGrid = Vector.fill(region.size.y, region.size.x)(false)
67-
helper(initialGrid, region.shapeCounts, shapeOrientationBox.iterator.toList)
68-
}*/
6972

73+
val initialGrid = Vector.fill(region.size.y, region.size.x)(false)
74+
helper(initialGrid, region.shapeCounts, shapeOrientationBox.iterator.toList)
75+
}
7076

71-
/*def fits(shapes: Seq[Shape])(region: Region): Boolean = {
77+
/*def fits(shapes: Seq[Shape])(region: Region): Boolean = {
7278
val shapeOrientationBox = Box(Pos.zero, region.size - Pos(3, 3)) // assuming all shappes of same size
7379
7480
def helper(grid: Grid[Boolean], shapeCounts: Seq[Int], poss: List[Pos]): Boolean = {
@@ -100,18 +106,31 @@ object Day12 {
100106
val initialGrid = Vector.fill(region.size.y, region.size.x)(false)
101107
helper(initialGrid, region.shapeCounts, shapeOrientationBox.iterator.toList)
102108
}*/
103-
104-
// cheat solution
105-
def fits(shapes: Seq[Shape])(region: Region): Boolean = {
106-
shapes
107-
.map(_.countGrid(identity))
108-
.lazyZip(region.shapeCounts)
109-
.map(_ * _)
110-
.sum <= Box(Pos.zero, region.size - Pos(1, 1)).size[Int]
111109
}
112110

113-
def countFits(input: Input): Int =
114-
input.regions.count(fits(input.shapes))
111+
object SanitySolution extends Solution {
112+
override def fits(shapes: Seq[Shape])(region: Region): Boolean = {
113+
val area = Box(Pos(1, 1), region.size).size[Int]
114+
val areaLowerBound = // only counting the #-s in shapes
115+
shapes
116+
.map(_.countGrid(identity))
117+
.lazyZip(region.shapeCounts)
118+
.map(_ * _)
119+
.sum
120+
val areaUpperBound = // counting area needed if no "overlaps" are possible
121+
shapes
122+
.map(_.sizeGrid)
123+
.lazyZip(region.shapeCounts)
124+
.map(_ * _)
125+
.sum
126+
if (areaUpperBound <= area)
127+
true
128+
else if (areaLowerBound > area)
129+
false
130+
else
131+
throw IllegalArgumentException("undecidable by sanity check")
132+
}
133+
}
115134

116135
def parseShape(s: String): Shape = s.linesIterator.tail.map(_.map(_ == '#').toVector).toVector
117136

@@ -128,6 +147,6 @@ object Day12 {
128147
lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day12.txt")).mkString.trim
129148

130149
def main(args: Array[String]): Unit = {
131-
println(countFits(parseInput(input)))
150+
println(SanitySolution.countFits(parseInput(input)))
132151
}
133152
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ 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") {
44-
val input1 = parseInput(exampleInput)
43+
ignore("Part 1 examples") { // TODO: optimize
44+
import NaiveSolution._ // doesn't work with cheat solution
45+
//val input1 = parseInput(exampleInput)
4546
//assert(fits(input1.shapes)(input1.regions(0)))
4647
//assert(fits(input1.shapes)(input1.regions(1)))
4748
//assert(!fits(input1.shapes)(input1.regions(2)))
48-
assert(countFits(input1) == 2) // doesn't work with cheat solution
49-
//parseInput(input)
49+
assert(countFits(parseInput(exampleInput)) == 2)
5050
}
5151

5252
test("Part 1 input answer") {
53-
assert(countFits(parseInput(input)) == 443)
53+
assert(SanitySolution.countFits(parseInput(input)) == 443)
5454
}
5555
}

0 commit comments

Comments
 (0)