Skip to content

Commit 3d10329

Browse files
committed
Day 12
1 parent 56f829d commit 3d10329

File tree

4 files changed

+157
-6
lines changed

4 files changed

+157
-6
lines changed

src/main/kotlin/common/datastructures/Grid.kt

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ class Grid<T>(val rows: List<List<Cell<T>>>) {
1919
Cell<T>(row, col, constructor(row, col))
2020
}.toList()
2121
}.toList())
22+
constructor(input: List<String>, constr: (row: Int, col: Int, c: Char) -> T) : this(Array(input.size) { row ->
23+
Array(input[0].length) { col ->
24+
Cell<T>(row, col, constr(row, col, input[row][col]))
25+
}.toList()
26+
}.toList())
2227

2328
operator fun get(row: Int) = rows[row]
2429
operator fun get(p: Point) = rows[p.row][p.col]

src/main/kotlin/y24/Day12.kt

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package y24
2+
3+
import common.puzzle.solvePuzzle
4+
import common.puzzle.Input
5+
import common.puzzle.Puzzle
6+
import common.datastructures.*
7+
8+
9+
fun main() = solvePuzzle(year = 2024, day = 12) { Day12(it) }
10+
11+
class Day12(val input: Input) : Puzzle {
12+
13+
data class Price(
14+
val area: Int,
15+
val perimeter: Int,
16+
) {
17+
val total: Int = area * perimeter
18+
}
19+
20+
private fun pricePerimeter(grid: Grid<Char>, cell: Cell<Char>, visited: MutableSet<Point>): Price {
21+
val p = cell.toPoint()
22+
if (p in visited) return Price(0, 0)
23+
24+
visited += p
25+
26+
var area = 1
27+
var perimeter = 0
28+
directions.forEach { dir ->
29+
val neigh = p + dir.toPoint()
30+
when {
31+
!grid.withinBounds(neigh.row, neigh.col) -> {
32+
perimeter++
33+
}
34+
grid[neigh].value != cell.value -> {
35+
perimeter++
36+
}
37+
else -> {
38+
val (neighArea, neighPerimeter) = pricePerimeter(grid, grid[neigh], visited)
39+
area += neighArea
40+
perimeter += neighPerimeter
41+
}
42+
}
43+
}
44+
45+
return Price(area, perimeter)
46+
}
47+
48+
private fun perimeter(grid: Grid<Char>, cell: Cell<Char>): List<Dir> {
49+
val p = cell.toPoint()
50+
return directions.mapNotNull { dir ->
51+
val neigh = p + dir.toPoint()
52+
when {
53+
!grid.withinBounds(neigh.row, neigh.col) -> dir
54+
grid[neigh].value != cell.value -> dir
55+
else -> null
56+
}
57+
}
58+
}
59+
60+
private fun priceNumSides(grid: Grid<Char>, cell: Cell<Char>, visited: MutableSet<Point>): Price {
61+
val disjointSets = DisjointSets<Pair<Point, Dir>>()
62+
val area = priceNumSidesInternal(grid, cell, null, emptyList(), visited, disjointSets)
63+
val numSides = disjointSets.sets.size
64+
return Price(area, numSides)
65+
}
66+
67+
private fun priceNumSidesInternal(grid: Grid<Char>, cell: Cell<Char>, from: Point?, fromPerimeter: List<Dir>, visited: MutableSet<Point>, disjointSets: DisjointSets<Pair<Point, Dir>>): Int {
68+
val p = cell.toPoint()
69+
val perimeter = perimeter(grid, cell)
70+
perimeter.forEach { disjointSets.find(p to it) }
71+
72+
from?.let {
73+
// Merge perimeter
74+
perimeter
75+
.filter { it in fromPerimeter }
76+
.forEach { dir ->
77+
// dir is shared perimeter between p and from
78+
disjointSets.union(p to dir, from to dir)
79+
}
80+
}
81+
82+
if (p in visited) {
83+
return 0
84+
}
85+
86+
visited += p
87+
return 1 + directions.sumOf { dir ->
88+
val neigh = p + dir.toPoint()
89+
when {
90+
!grid.withinBounds(neigh.row, neigh.col) -> 0
91+
grid[neigh].value != cell.value -> 0
92+
else -> priceNumSidesInternal(grid, grid[neigh], p, perimeter, visited, disjointSets)
93+
}
94+
}
95+
}
96+
97+
private val grid = Grid(input.lines) { _, _, c -> c }
98+
99+
override fun solveLevel1(): Any {
100+
val visited = mutableSetOf<Point>()
101+
var totalCost = 0
102+
103+
grid.cells().forEach { cell ->
104+
totalCost += pricePerimeter(grid, cell, visited).total
105+
}
106+
107+
return totalCost
108+
}
109+
110+
override fun solveLevel2(): Any {
111+
val visited = mutableSetOf<Point>()
112+
var totalCost = 0
113+
114+
grid.cells().forEach { cell ->
115+
totalCost += priceNumSides(grid, cell, visited).total
116+
}
117+
118+
return totalCost
119+
}
120+
}

src/test/kotlin/common/ext/NumberTest.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import org.junit.jupiter.api.Assertions.*
77
internal class NumberTest {
88
@Test
99
fun numDigits() {
10-
assertEquals(1, 0.numDigits())
11-
assertEquals(1, 1.numDigits())
12-
assertEquals(1, 9.numDigits())
13-
assertEquals(2, 10.numDigits())
14-
assertEquals(2, 99.numDigits())
15-
assertEquals(3, 100.numDigits())
10+
assertEquals(1, 0L.numDigits())
11+
assertEquals(1, 1L.numDigits())
12+
assertEquals(1, 9L.numDigits())
13+
assertEquals(2, 10L.numDigits())
14+
assertEquals(2, 99L.numDigits())
15+
assertEquals(3, 100L.numDigits())
1616
}
1717
}

src/test/kotlin/y24/Day12Test.kt

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package y24
2+
3+
import common.puzzle.Input
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Test
6+
7+
internal class Day12Test {
8+
private val sample = Input("""
9+
AAAA
10+
BBCD
11+
BBCC
12+
EEEC
13+
""".trimIndent())
14+
15+
private val day = Day12(sample)
16+
17+
@Test
18+
fun solveLevel1() {
19+
assertEquals(140, day.solveLevel1())
20+
}
21+
22+
@Test
23+
fun solveLevel2() {
24+
assertEquals(80, day.solveLevel2())
25+
}
26+
}

0 commit comments

Comments
 (0)