Skip to content

Commit 70e8c2a

Browse files
committed
Implement a MapGridBackend
= a grid backend based on a mutable map
1 parent 2de5565 commit 70e8c2a

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

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

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

3+
import kotlin.math.max
4+
import kotlin.math.min
5+
36
interface GridBackend<T> {
47
operator fun get(x: Int, y: Int): T
58
operator fun get(at: Coordinates): T
@@ -57,5 +60,55 @@ class ListGridBackend<T>(width: Int, height: Int, nullElement: T) : GridBackend<
5760
}
5861
}
5962
}
63+
}
64+
65+
class MapGridBackend<T>(defaultValue: T) : GridBackend<T> {
66+
private var minX = Int.MAX_VALUE
67+
private var maxX = Int.MIN_VALUE
68+
private var minY = Int.MAX_VALUE
69+
private var maxY = Int.MIN_VALUE
70+
71+
private val grid = mutableMapOf<Coordinates, T>().withDefault { defaultValue }
72+
73+
override fun get(x: Int, y: Int): T = get(Coordinates(x, y))
74+
75+
override fun get(at: Coordinates): T {
76+
if (at.x !in minX..maxX || at.y !in minY..maxY) {
77+
throw IndexOutOfBoundsException("Coordinates $at are not inside the bound of this Grid (min and max coordinates for which values were set)")
78+
}
79+
return grid.getValue(at)
80+
}
81+
82+
override fun set(x: Int, y: Int, value: T) = set(Coordinates(x, y), value)
83+
84+
override fun set(at: Coordinates, value: T) {
85+
minX = min(minX, at.x)
86+
minY = min(minY, at.y)
87+
maxX = max(maxX, at.x)
88+
maxY = max(maxY, at.y)
89+
grid[at] = value
90+
}
91+
92+
override fun getOrNull(x: Int, y: Int): T? = grid[Coordinates(x, y)]
93+
94+
override fun getOrNull(at: Coordinates): T? = grid[at]
95+
96+
override fun subGridAt(x: Int, y: Int, width: Int, height: Int): List<List<T>> = buildList {
97+
for (row in y..<y + height) {
98+
add(buildList {
99+
for (col in x..<x + width) {
100+
add(grid.getValue(Coordinates(col, row)))
101+
}
102+
}
103+
)
104+
}
105+
}
60106

107+
override fun <R> mapToSequence(transform: (x: Int, y: Int) -> R): Sequence<R> = sequence {
108+
for (row in minY..maxY) {
109+
for (col in minX..maxX) {
110+
yield(transform(col, row))
111+
}
112+
}
113+
}
61114
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package de.ronny_h.aoc.extensions.grids
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.core.spec.style.StringSpec
5+
import io.kotest.data.forAll
6+
import io.kotest.data.row
7+
import io.kotest.matchers.shouldBe
8+
9+
class MapGridBackendTest : StringSpec({
10+
11+
"get returns the existing element: the one that's set or the fallback element" {
12+
val grid: GridBackend<Char> = MapGridBackend('#')
13+
grid[0, 0] = '0'
14+
grid[Coordinates(1, 1)] = '1'
15+
16+
forAll(
17+
row(0, 0, '0'),
18+
row(0, 1, '#'),
19+
row(1, 0, '#'),
20+
row(1, 1, '1'),
21+
) { x, y, expected ->
22+
grid[x, y] shouldBe expected
23+
grid[Coordinates(x, y)] shouldBe expected
24+
}
25+
}
26+
27+
"get for an element out of the bound of the elements set throws an Exception" {
28+
val grid: GridBackend<Char> = MapGridBackend('#')
29+
grid[0, 0] = '0'
30+
grid[Coordinates(1, 1)] = '1'
31+
32+
shouldThrow<IndexOutOfBoundsException> {
33+
grid[3, 0]
34+
}
35+
}
36+
37+
"getOrNull returns null for non existing elements" {
38+
val grid: GridBackend<Char> = MapGridBackend('#')
39+
grid[0, 0] = '0'
40+
grid[Coordinates(1, 1)] = '1'
41+
42+
forAll(
43+
row(0, 0, '0'),
44+
row(0, 1, null),
45+
row(1, 0, null),
46+
row(1, 1, '1'),
47+
row(2, 0, null),
48+
row(0, 2, null),
49+
) { x, y, expected ->
50+
grid.getOrNull(x, y) shouldBe expected
51+
grid.getOrNull(Coordinates(x, y)) shouldBe expected
52+
}
53+
}
54+
55+
"subGridAt returns a list of lists for the specified section" {
56+
val grid: GridBackend<Char> = MapGridBackend('#')
57+
grid[0, 0] = '0'
58+
grid[Coordinates(1, 1)] = '1'
59+
grid[Coordinates(2, 2)] = '2'
60+
61+
grid.subGridAt(1, 1, 2, 2) shouldBe listOf(
62+
listOf('1', '#'),
63+
listOf('#', '2'),
64+
)
65+
}
66+
67+
"mapToSequence transforms each grid coordinates row by row" {
68+
val grid = MapGridBackend(0)
69+
grid[1, 0] = 1
70+
grid[0, 1] = 2
71+
grid[1, 1] = 3
72+
73+
grid.mapToSequence { x, y ->
74+
"${grid[x, y]}"
75+
}.toList() shouldBe listOf("0", "1", "2", "3")
76+
}
77+
})

0 commit comments

Comments
 (0)