Skip to content

Commit 79b2d47

Browse files
committed
Generalize the clusterRegions() function
... and use it in DiskDefragmentation and GardenGroups.
1 parent 0dfa5a0 commit 79b2d47

File tree

4 files changed

+94
-62
lines changed

4 files changed

+94
-62
lines changed

src/main/kotlin/de/ronny_h/aoc/extensions/grids/SimpleCharGrid.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,34 @@ package de.ronny_h.aoc.extensions.grids
33

44
open class SimpleCharGrid(input: List<String>, nullElement: Char = '#') : Grid<Char>(input, nullElement) {
55
override fun Char.toElementType() = this
6+
7+
fun clusterRegions(char: Char? = null): List<List<Coordinates>> {
8+
val assigned = mutableSetOf<Coordinates>()
9+
return forEachCoordinates { position, element ->
10+
if ((char == null || element == char) && position !in assigned) {
11+
val region = collectRegionAt(position, element)
12+
assigned.addAll(region)
13+
region
14+
} else {
15+
null
16+
}
17+
}
18+
.filterNotNull()
19+
.toList()
20+
}
21+
22+
private fun collectRegionAt(
23+
position: Coordinates,
24+
char: Char,
25+
visited: MutableSet<Coordinates> = mutableSetOf(position),
26+
regionsCoordinates: MutableList<Coordinates> = mutableListOf(position),
27+
): List<Coordinates> {
28+
position.neighbours().forEach { coordinates ->
29+
if (getAt(coordinates) == char && visited.add(coordinates)) {
30+
regionsCoordinates.add(coordinates)
31+
collectRegionAt(coordinates, char, visited, regionsCoordinates)
32+
}
33+
}
34+
return regionsCoordinates
35+
}
636
}

src/main/kotlin/de/ronny_h/aoc/year2017/day14/DiskDefragmentation.kt

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package de.ronny_h.aoc.year2017.day14
22

33
import de.ronny_h.aoc.AdventOfCode
4-
import de.ronny_h.aoc.extensions.grids.Coordinates
54
import de.ronny_h.aoc.extensions.grids.SimpleCharGrid
65
import de.ronny_h.aoc.year2017.day10.knotHash
76
import java.math.BigInteger
@@ -24,42 +23,12 @@ class DiskDefragmentation : AdventOfCode<Int>(2017, 14) {
2423
.single()
2524
.toBinaryKnotHashes()
2625

27-
return Disk(binaryKnotHashes)
28-
.clusterRegions()
26+
return SimpleCharGrid(binaryKnotHashes, '0')
27+
.clusterRegions('1')
2928
.size
3029
}
3130
}
3231

33-
class Disk(input: List<String>) : SimpleCharGrid(input, '0') {
34-
35-
fun clusterRegions(): List<List<Coordinates>> {
36-
val assigned = mutableSetOf<Coordinates>()
37-
return forEachCoordinates { position, element ->
38-
if (element == '1' && position !in assigned) {
39-
collectRegionAt(position, assigned)
40-
} else {
41-
null
42-
}
43-
}
44-
.filterNotNull()
45-
.toList()
46-
}
47-
48-
private fun collectRegionAt(
49-
position: Coordinates,
50-
visited: MutableSet<Coordinates> = mutableSetOf(position),
51-
regionsCoordinates: MutableList<Coordinates> = mutableListOf(position),
52-
): List<Coordinates> {
53-
position.neighbours().forEach { coordinates ->
54-
if (getAt(coordinates) == '1' && visited.add(coordinates)) {
55-
regionsCoordinates.add(coordinates)
56-
collectRegionAt(coordinates, visited, regionsCoordinates)
57-
}
58-
}
59-
return regionsCoordinates
60-
}
61-
}
62-
6332
fun hexToBinary(hexString: String): String =
6433
hexString
6534
.map { ch -> BigInteger("$ch", 16).toString(2).padStart(4, '0') }

src/main/kotlin/de/ronny_h/aoc/year2024/day12/GardenGroups.kt

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import de.ronny_h.aoc.AdventOfCode
44
import de.ronny_h.aoc.extensions.grids.Coordinates
55
import de.ronny_h.aoc.extensions.grids.Direction
66
import de.ronny_h.aoc.extensions.grids.Direction.NORTH
7-
import de.ronny_h.aoc.extensions.grids.Grid
7+
import de.ronny_h.aoc.extensions.grids.SimpleCharGrid
88

99
const val verbose = false
1010

@@ -22,50 +22,26 @@ class GardenGroups : AdventOfCode<Int>(2024, 12) {
2222

2323
private data class Region(val area: Int, val perimeter: Int, val numberOfSides: Int)
2424

25-
private class Farm(input: List<String>) : Grid<Char>(input, ' ') {
26-
override fun Char.toElementType() = this
25+
private class Farm(input: List<String>) : SimpleCharGrid(input, ' ') {
2726

28-
private val assigned = mutableSetOf<Coordinates>()
29-
30-
fun separateRegions(): List<Region> = forEachCoordinates { position, plant ->
31-
if (position !in assigned) {
32-
val region = collectRegionPlotsAt(position, plant)
27+
fun separateRegions(): List<Region> = clusterRegions()
28+
.map { region ->
29+
val plant = getAt(region.first())
3330
val perimeter = perimeterOf(region, plant)
3431
val numberOfSides = numberOfSidesOf(region, plant)
35-
assigned.addAll(region)
3632
if (verbose) {
3733
val regionAsSet = region.toSet()
3834
check(region.size == regionAsSet.size)
3935
printGrid(regionAsSet)
4036
println("---------- area: ${region.size} perimeter: $perimeter")
4137
}
4238
Region(region.size, perimeter, numberOfSides)
43-
} else {
44-
null
4539
}
46-
}
47-
.filterNotNull()
48-
.toList()
4940

5041
private fun perimeterOf(region: List<Coordinates>, plant: Char) = region.sumOf {
5142
it.neighbours().filter { n -> getAt(n) != plant }.size
5243
}
5344

54-
private fun collectRegionPlotsAt(
55-
position: Coordinates,
56-
plant: Char,
57-
regionsCoordinates: MutableList<Coordinates> = mutableListOf(position),
58-
visited: MutableSet<Coordinates> = mutableSetOf(position)
59-
): List<Coordinates> {
60-
position.neighbours().forEach { coordinates ->
61-
if (getAt(coordinates) == plant && visited.add(coordinates)) {
62-
regionsCoordinates.add(coordinates)
63-
collectRegionPlotsAt(coordinates, plant, regionsCoordinates, visited)
64-
}
65-
}
66-
return regionsCoordinates
67-
}
68-
6945
private fun numberOfSidesOf(region: List<Coordinates>, plant: Char): Int {
7046
val boundary = region.filter { it.neighbours().any { n -> getAt(n) != plant } }
7147
val visited = mutableSetOf<Pair<Coordinates, Direction>>()

src/test/kotlin/de/ronny_h/aoc/extensions/grids/SimpleCharGridTest.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.ronny_h.aoc.extensions.grids
22

3+
import de.ronny_h.aoc.extensions.asList
34
import de.ronny_h.aoc.extensions.graphs.ShortestPath
45
import io.kotest.core.spec.style.StringSpec
56
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
@@ -107,4 +108,60 @@ class SimpleCharGridTest : StringSpec({
107108
),
108109
)
109110
}
111+
112+
"cluster regions of same char with a single region" {
113+
val input = """
114+
xx
115+
""".asList()
116+
SimpleCharGrid(input).clusterRegions() shouldBe listOf(
117+
listOf(Coordinates(0, 0), Coordinates(0, 1)),
118+
)
119+
}
120+
121+
"cluster regions of same char with two regions" {
122+
val input = """
123+
..xx
124+
..xx
125+
""".asList()
126+
SimpleCharGrid(input).clusterRegions() shouldBe listOf(
127+
listOf(Coordinates(0, 0), Coordinates(0, 1), Coordinates(1, 1), Coordinates(1, 0)),
128+
listOf(Coordinates(0, 2), Coordinates(0, 3), Coordinates(1, 3), Coordinates(1, 2)),
129+
)
130+
}
131+
132+
"cluster regions of same char with four regions" {
133+
val input = """
134+
..xx
135+
oo__
136+
""".asList()
137+
SimpleCharGrid(input).clusterRegions() shouldBe listOf(
138+
listOf(Coordinates(0, 0), Coordinates(0, 1)),
139+
listOf(Coordinates(0, 2), Coordinates(0, 3)),
140+
listOf(Coordinates(1, 0), Coordinates(1, 1)),
141+
listOf(Coordinates(1, 2), Coordinates(1, 3)),
142+
)
143+
}
144+
145+
"cluster one region of a single, specified char" {
146+
val input = """
147+
..x..
148+
.xxx.
149+
..x..
150+
""".asList()
151+
SimpleCharGrid(input).clusterRegions('x') shouldBe listOf(
152+
listOf(
153+
Coordinates(0, 2), Coordinates(1, 2), Coordinates(1, 3),
154+
Coordinates(2, 2), Coordinates(1, 1)
155+
),
156+
)
157+
}
158+
159+
"cluster regions with no region matching the specified char" {
160+
val input = """
161+
..x..
162+
.xxx.
163+
..x..
164+
""".asList()
165+
SimpleCharGrid(input).clusterRegions('o') shouldBe emptyList()
166+
}
110167
})

0 commit comments

Comments
 (0)