Skip to content

Commit 0dfa5a0

Browse files
committed
Pull up shortestPaths() to Grid, add some documentation
The function was implemented in SimpleCharGrid but does not rely on any specifics of that sub-class.
1 parent 7ae73c5 commit 0dfa5a0

File tree

3 files changed

+74
-26
lines changed

3 files changed

+74
-26
lines changed

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

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

3+
import de.ronny_h.aoc.extensions.graphs.ShortestPath
4+
import de.ronny_h.aoc.extensions.graphs.aStarAllPaths
35
import java.io.ByteArrayOutputStream
46
import java.io.PrintStream
57
import java.nio.charset.StandardCharsets
68

79
/**
10+
* A 2-dimensional, rectangular Grid where each cell is identified by its [Coordinates] and represented by a value of type [T].
11+
*
12+
* @param height The number of rows in the Grid.
13+
* @param width The number of columns in the Grid.
814
* @param nullElement The initial element within the grid
9-
* @param overrideElement Used for out of bound positions. Defaults to `nullElement`
15+
* @param fallbackElement Used for out of bound positions. Defaults to [nullElement].
1016
*/
1117
abstract class Grid<T>(
1218
val height: Int,
1319
val width: Int,
1420
protected val nullElement: T,
15-
private val overrideElement: T = nullElement,
21+
private val fallbackElement: T = nullElement,
1622
) {
1723

1824
private val grid: MutableList<MutableList<T>> = MutableList(height) { MutableList(width) { nullElement } }
1925

26+
/**
27+
* A function that maps each `Char` that may occur in the `input: List<String>` to a value of type [T].
28+
*/
2029
abstract fun Char.toElementType(): T
2130

31+
/**
32+
* Constructs a Grid with the specified dimensions filled with the [nullElement] as default value and all
33+
* coordinates in [overrides] set to [overrideElement].
34+
*/
2235
constructor(
2336
height: Int,
2437
width: Int,
@@ -31,6 +44,12 @@ abstract class Grid<T>(
3144
}
3245
}
3346

47+
/**
48+
* Constructs a Grid from the [input] list. Uses function [toElementType] for converting the [String]s' [Char]s
49+
* to values of type [T].
50+
*
51+
* @param input A list of `String`s of the same length that determines the Grid's width. Its size determines the Grid's height.
52+
*/
3453
constructor(input: List<String>, nullElement: T, overrideElement: T = nullElement) : this(
3554
input.size,
3655
input[0].length,
@@ -41,15 +60,18 @@ abstract class Grid<T>(
4160
initGrid(input)
4261
}
4362

44-
fun initGrid(input: List<String>) = input.forEachIndexed { row, line ->
63+
protected fun initGrid(input: List<String>) = input.forEachIndexed { row, line ->
4564
line.forEachIndexed { col, char -> grid[row][col] = char.toElementType() }
4665
}
4766

67+
/**
68+
* @return The value at the specified cell.
69+
*/
4870
operator fun get(row: Int, col: Int): T {
4971
return grid
5072
.getOrNull(row)
5173
?.getOrNull(col)
52-
?: overrideElement
74+
?: fallbackElement
5375
}
5476

5577
fun getAt(position: Coordinates) = get(position.row, position.col)
@@ -82,7 +104,7 @@ abstract class Grid<T>(
82104
}
83105

84106
/**
85-
* Returns the first element matching the given `value`.
107+
* Returns the first element matching the given [value].
86108
*
87109
* @throws NoSuchElementException If no matching element can be found.
88110
*/
@@ -92,6 +114,10 @@ abstract class Grid<T>(
92114

93115
override fun toString(): String = toString(setOf())
94116

117+
/**
118+
* @return A String representation of this [Grid] with all coordinates in [overrides] overridden
119+
* by the Char specified as [overrideChar].
120+
*/
95121
fun toString(
96122
overrides: Set<Coordinates> = setOf(),
97123
overrideChar: Char = '#',
@@ -103,6 +129,12 @@ abstract class Grid<T>(
103129
return out.toString().trim()
104130
}
105131

132+
/**
133+
* Prints this [Grid] to [System.out] with (the first matching rule applies):
134+
* - if not `null`, the [highlightPosition] overridden by the `Char` representation of [highlightDirection]
135+
* - each coordinate in [path] overridden by the `Char` that is mapped to it
136+
* - all coordinates in [overrides] overridden by the `Char` specified as [overrideChar]
137+
*/
106138
fun printGrid(
107139
overrides: Set<Coordinates> = setOf(),
108140
overrideChar: Char = '#',
@@ -113,7 +145,7 @@ abstract class Grid<T>(
113145
printGrid(System.out, overrides, overrideChar, highlightPosition, highlightDirection, path)
114146
}
115147

116-
fun printGrid(
148+
private fun printGrid(
117149
writer: PrintStream,
118150
overrides: Set<Coordinates> = setOf(),
119151
overrideChar: Char = '#',
@@ -134,4 +166,26 @@ abstract class Grid<T>(
134166
if (position.col == width - 1) writer.println()
135167
}.last()
136168
}
169+
170+
/**
171+
* Determines all shortest paths possible from [start] to [goal] with the following conditions:
172+
* - The Grid's [nullElement] is the obstacle.
173+
* - No path outside the Grid is possible.
174+
* - Only direct neighbours (no diagonal ones) are considered.
175+
* - The cost of moving to a neighbour equals 1.
176+
*/
177+
fun shortestPaths(start: Coordinates, goal: Coordinates): List<ShortestPath<Coordinates>> {
178+
val neighbours: (Coordinates) -> List<Coordinates> = { position ->
179+
position.neighbours().filter { getAt(it) != nullElement }
180+
}
181+
182+
val d: (Coordinates, Coordinates) -> Int = { a, b ->
183+
require(a taxiDistanceTo b == 1) { "a and b have to be neighbours" }
184+
1
185+
}
186+
187+
val h: (Coordinates) -> Int = { it taxiDistanceTo goal }
188+
189+
return aStarAllPaths(start, { this == goal }, neighbours, d, h)
190+
}
137191
}
Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,6 @@
11
package de.ronny_h.aoc.extensions.grids
22

3-
import de.ronny_h.aoc.extensions.graphs.ShortestPath
4-
import de.ronny_h.aoc.extensions.graphs.aStarAllPaths
53

64
open class SimpleCharGrid(input: List<String>, nullElement: Char = '#') : Grid<Char>(input, nullElement) {
75
override fun Char.toElementType() = this
8-
9-
fun shortestPaths(start: Coordinates, goal: Coordinates): List<ShortestPath<Coordinates>> {
10-
val neighbours: (Coordinates) -> List<Coordinates> = { position ->
11-
Direction
12-
.entries
13-
.map { position + it }
14-
.filter { getAt(it) != nullElement }
15-
}
16-
17-
val d: (Coordinates, Coordinates) -> Int = { a, b ->
18-
check(a taxiDistanceTo b == 1) // pre-condition: a and b a neighbours
19-
1
20-
}
21-
22-
val h: (Coordinates) -> Int = { it taxiDistanceTo goal }
23-
24-
return aStarAllPaths(start, { this == goal }, neighbours, d, h)
25-
}
266
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,18 @@ class SimpleCharGridTest : StringSpec({
9393
ShortestPath(listOf(Coordinates(0, 1), Coordinates(1, 1), Coordinates(1, 2)), 2),
9494
)
9595
}
96+
97+
"obstacles are not part of the shortest path" {
98+
SimpleCharGrid(listOf("0#2", "3#5", "678"), '#').shortestPaths(
99+
Coordinates(0, 0),
100+
Coordinates(0, 2)
101+
) shouldBe listOf(
102+
ShortestPath(
103+
listOf(
104+
Coordinates(0, 0), Coordinates(1, 0), Coordinates(2, 0),
105+
Coordinates(2, 1), Coordinates(2, 2), Coordinates(1, 2), Coordinates(0, 2)
106+
), 6
107+
),
108+
)
109+
}
96110
})

0 commit comments

Comments
 (0)