-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add generic Grid2d and Point2d data classes and use them in 2024/12
- Loading branch information
Showing
5 changed files
with
251 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package adventofcode.common | ||
|
||
import kotlin.String | ||
|
||
data class Point2d( | ||
val x: Int, | ||
val y: Int, | ||
) { | ||
/** | ||
* Pretty formatted String representation. | ||
* | ||
* (0, 1) | ||
*/ | ||
override fun toString(): String = "($x, $y)" | ||
|
||
/** | ||
* Add two points together by adding their x and y coordinates | ||
*/ | ||
operator fun plus(other: Point2d): Point2d = Point2d(x + other.x, y + other.y) | ||
} | ||
|
||
val NORTH = Point2d(0, -1) | ||
val NORTHEAST = Point2d(1, -1) | ||
val EAST = Point2d(1, 0) | ||
val SOUTHEAST = Point2d(1, 1) | ||
val SOUTH = Point2d(0, 1) | ||
val SOUTHWEST = Point2d(-1, 1) | ||
val WEST = Point2d(-1, 0) | ||
val NORTHWEST = Point2d(-1, -1) | ||
|
||
data class Grid2d<T>(val values: List<List<T>>) { | ||
val points = values.flatMapIndexed { y, row -> List(row.size) { x -> Point2d(x, y) } } | ||
val isSquare: Boolean = values.all { row -> row.size == values.size } | ||
|
||
/** | ||
* Pretty formatted String representation. | ||
* | ||
* [A, B, C] | ||
* [D, E, F] | ||
* [G, H, I] | ||
*/ | ||
override fun toString(): String = values.joinToString("\n") { row -> row.toString() } | ||
|
||
/** | ||
* Returns `true` if the grid contains `value` | ||
*/ | ||
operator fun contains(value: T): Boolean = values.flatten().contains(value) | ||
|
||
/** | ||
* Returns `true` if the point is within the grid. | ||
*/ | ||
operator fun contains(point: Point2d): Boolean = point in points | ||
|
||
/** | ||
* Returns the points of all instances of `value` if the grid contains it. | ||
*/ | ||
fun find(value: T): List<Point2d> = | ||
points | ||
.filter { (x, y) -> values[y][x] == value } | ||
.map { (x, y) -> Point2d(x, y) } | ||
|
||
/** | ||
* Returns the value at the given point if the point is within the grid, throws otherwise. | ||
*/ | ||
operator fun get(point: Point2d): T = | ||
if (point in this) values[point.y][point.x] else throw IndexOutOfBoundsException("Point $point is not part of this grid") | ||
|
||
/** | ||
* Returns the value at the given point if the point is within the grid, or `null` otherwise. | ||
*/ | ||
fun getOrNull(point: Point2d): T? = if (point in this) values[point.y][point.x] else null | ||
|
||
/** | ||
* Returns a set of valid neighbors for a given point P in the grid. | ||
* | ||
* Excluding diagonals: Including diagonals: | ||
* _ N _ N N N | ||
* N P N N P N | ||
* _ N _ N N N | ||
* where N is a neighbor of P. | ||
*/ | ||
fun neighborsOf( | ||
point: Point2d, | ||
includeDiagonals: Boolean = false, | ||
): Set<Point2d> = | ||
when { | ||
includeDiagonals -> setOf(NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST) | ||
else -> setOf(NORTH, EAST, SOUTH, WEST) | ||
} | ||
.map { direction -> point + direction } | ||
.filter { it in this } | ||
.toSet() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package adventofcode.common | ||
|
||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.core.spec.style.FreeSpec | ||
import io.kotest.matchers.collections.beEmpty | ||
import io.kotest.matchers.collections.shouldContainExactly | ||
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder | ||
import io.kotest.matchers.should | ||
import io.kotest.matchers.shouldBe | ||
|
||
class Grid2dSpec : FreeSpec({ | ||
val grid = | ||
Grid2d( | ||
listOf( | ||
listOf('A', 'B', 'C'), | ||
listOf('D', 'E', 'F'), | ||
listOf('G', 'H', 'I'), | ||
), | ||
) | ||
|
||
"isSquare" - { | ||
"is `true` for a square grid" { | ||
grid.isSquare shouldBe true | ||
} | ||
|
||
"is `false` for a non-square grid" { | ||
Grid2d( | ||
listOf( | ||
listOf('A', 'B', 'C', 'D'), | ||
listOf('E', 'F', 'G', 'H'), | ||
listOf('I', 'J', 'K', 'L'), | ||
), | ||
).isSquare shouldBe false | ||
} | ||
} | ||
|
||
"contains value operator" - { | ||
"returns `true` if the grid contains that value" { | ||
('E' in grid) shouldBe true | ||
} | ||
|
||
"returns `false` if the grid does not contain that value" { | ||
('Z' in grid) shouldBe false | ||
} | ||
} | ||
|
||
"contains Point2d operator" - { | ||
"returns `true` if the grid contains that point" { | ||
(Point2d(0, 0) in grid) shouldBe true | ||
(Point2d(1, 1) in grid) shouldBe true | ||
} | ||
|
||
"returns `false` if the grid does not contain that value" { | ||
(Point2d(0, -1) in grid) shouldBe false | ||
(Point2d(4, 0) in grid) shouldBe false | ||
} | ||
} | ||
|
||
"find" - { | ||
"returns the point of that value if the grid contains that value exactly once" { | ||
grid.find('E') shouldContainExactly listOf(Point2d(1, 1)) | ||
} | ||
|
||
"returns the points of all instances if the grid contains that value" { | ||
Grid2d( | ||
listOf( | ||
listOf('A', 'B', 'C'), | ||
listOf('D', 'A', 'F'), | ||
listOf('G', 'H', 'A'), | ||
), | ||
).find('A') shouldContainExactly listOf(Point2d(0, 0), Point2d(1, 1), Point2d(2, 2)) | ||
} | ||
|
||
"returns an empty list if the grid does not contain that value" { | ||
grid.find('Z') should beEmpty() | ||
} | ||
} | ||
|
||
"get" - { | ||
"returns the value at the given point if it exists" { | ||
grid[Point2d(2, 1)] shouldBe 'F' | ||
} | ||
|
||
"throws if the point is not part of the grid" { | ||
shouldThrow<IndexOutOfBoundsException> { | ||
grid[Point2d(10, 10)] | ||
}.apply { | ||
message shouldBe "Point (10, 10) is not part of this grid" | ||
} | ||
} | ||
} | ||
|
||
"getOrNull" - { | ||
"returns the value at the given point if it exists" { | ||
grid.getOrNull(Point2d(2, 1)) shouldBe 'F' | ||
} | ||
|
||
"returns null if the point is not part of the grid" { | ||
grid.getOrNull(Point2d(10, 10)) shouldBe null | ||
} | ||
} | ||
|
||
"neighborsOf" - { | ||
"returns the cardinal neighbors of a point in the grid" { | ||
grid.neighborsOf(Point2d(1, 1)) shouldContainExactlyInAnyOrder setOf(Point2d(1, 0), Point2d(2, 1), Point2d(1, 2), Point2d(0, 1)) | ||
} | ||
|
||
"returns only valid cardinal neighbors of a point on the edge of grid" { | ||
grid.neighborsOf(Point2d(0, 0)) shouldContainExactlyInAnyOrder setOf(Point2d(1, 0), Point2d(0, 1)) | ||
grid.neighborsOf(Point2d(2, 1)) shouldContainExactlyInAnyOrder setOf(Point2d(2, 0), Point2d(2, 2), Point2d(1, 1)) | ||
} | ||
|
||
"returns cardinal and diagonal neighbors of a point in the grid when includeDiagonals is `true`" { | ||
grid.neighborsOf(Point2d(1, 1), includeDiagonals = true) shouldContainExactlyInAnyOrder | ||
setOf( | ||
Point2d(1, 0), | ||
Point2d(2, 0), | ||
Point2d(2, 1), | ||
Point2d(2, 2), | ||
Point2d(1, 2), | ||
Point2d(0, 2), | ||
Point2d(0, 1), | ||
Point2d(0, 0), | ||
) | ||
} | ||
} | ||
|
||
"toString" { | ||
grid.toString() shouldBe | ||
""" | ||
[A, B, C] | ||
[D, E, F] | ||
[G, H, I] | ||
""".trimIndent() | ||
} | ||
}) |