Brroken: Changes to backing data structures in the name of speed

main
Bryson Zimmerman 12 months ago
parent 14e9b99189
commit c07952bec1

@ -0,0 +1,29 @@
<component name="libraryTable">
<library name="it.unimi.dsi.dsiutils" type="repository">
<properties maven-id="it.unimi.dsi:dsiutils:2.7.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/it/unimi/dsi/dsiutils/2.7.3/dsiutils-2.7.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.3/slf4j-api-2.0.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/ch/qos/logback/logback-core/1.3.4/logback-core-1.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/ch/qos/logback/logback-classic/1.3.4/logback-classic-1.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/sun/mail/javax.mail/1.6.2/javax.mail-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/activation/activation/1.1/activation-1.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/it/unimi/dsi/fastutil/8.5.12/fastutil-8.5.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/it/unimi/di/jsap/20210129/jsap-20210129.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-configuration2/2.8.0/commons-configuration2-2.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.9/commons-text-1.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.2/commons-logging-1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/31.1-jre/guava-31.1-jre.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.11.0/error_prone_annotations-2.11.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -5,13 +5,15 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="741ceb24-e493-47c1-9f25-1dbd8b151381" name="Changes" comment="Implemented MazeFinder, significant changes to supporting data structures to match developed understanding of Kotlin and Object-Oriented philosophy"> <list default="true" id="741ceb24-e493-47c1-9f25-1dbd8b151381" name="Changes" comment="Implemented MazeFinder, significant changes to supporting data structures to match developed understanding of Kotlin and Object-Oriented philosophy">
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/Main.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/Main.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/kotlin/Main.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/Main.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/Mazefinder.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/MazeFinder.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/kotlin/MazeFinder.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/MazeFinder.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/World.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/World.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/Directions.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/Directions.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/kotlin/data/Directions.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/Directions.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/TileProperties.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/TileProperties.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/kotlin/data/TileProperties.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/TileProperties.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/WorldData.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/WorldData.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/kotlin/TileTest.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/kotlin/TileTest.kt" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />

@ -14,7 +14,7 @@ class HierarchicalPathfinding {
companion object { companion object {
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
val n = 5000 val n = 10
println("Building world") println("Building world")
World.setSize(n, n) World.setSize(n, n)
println("Start") println("Start")

@ -31,31 +31,37 @@ object MazeFinder {
//Prime the graph with the first connection, which marks the first visited Tiles
startingTile = World.getRandomLocation() startingTile = World.getRandomLocation()
startingTile.getProperties().visited = true val tmpIndex = randGen.nextInt(Directions.ALL.size)
startingTile.connect(Directions.ALL.get(tmpIndex))
//Choose an arbitrary vertex from G (the graph), and add it to some (initially empty) set V. //Choose an arbitrary vertex from G (the graph), and add it to some (initially empty) set V.
frontier.addAll(startingTile.getAdjacentTiles(false)) frontier.addAll(startingTile.getAdjacentTiles(false))
frontier.addAll((startingTile + Directions.ALL.get(tmpIndex)).getAdjacentTiles(false))
//Choose a random edge from G, that connects a vertex in V with another vertex not in V. //Choose a random edge from G, that connects a vertex in V with another vertex not in V.
var current: Tile var current: Tile
var inGraph: Pair<Tile, Directions> var inGraph: Tile
var adjacentExplored: Set<Pair<Tile, Directions>> var adjacentExplored: Set<Tile>
while(frontier.isNotEmpty()) { while(frontier.isNotEmpty()) {
//Grab a random tile from the frontier, mark it as visited //Grab a random tile from the frontier, mark it as visited
val random = randGen.nextInt(frontier.size()) val random = randGen.nextInt(frontier.size())
current = frontier.data[random] current = frontier.data.get(random)
current.getProperties().visited = true current.getProperties().visited()
frontier.removeAt(random) frontier.removeAt(random)
//Find adjacent tiles that are in the graph //Find adjacent tiles that are in the graph
adjacentExplored = current.getAdjacent(true) adjacentExplored = current.getAdjacentTiles(true)
if(adjacentExplored.isEmpty())
println("No explored adjacent tiles found")
//Select a random tile from possibleTiles //Select a random tile from possibleTiles
inGraph = adjacentExplored.elementAt(randGen.nextInt(adjacentExplored.size)) inGraph = adjacentExplored.elementAt(randGen.nextInt(adjacentExplored.size))
//Connect the frontier tile to the graph //Connect the frontier tile to the graph
current.connect(inGraph.second) current.connect(Directions.convertModifier(current.value - inGraph.value))
//Add current's unexplored tiles to the frontier, if not already in frontier //Add current's unexplored tiles to the frontier, if not already in frontier
frontier.addAll(current.getAdjacentTiles(false)) frontier.addAll(current.getAdjacentTiles(false))

@ -20,9 +20,13 @@ object World {
//Default size should be 20 //Default size should be 20
val tiles = WorldData(ArrayList<ArrayList<TileProperties>>()) val tiles = WorldData(ArrayList<ArrayList<TileProperties>>())
fun update(tile: Tile, to: TileProperties) {
tiles.value[tile.x()][tile.y()] = to
}
//Returns a coordinate pair //Returns a coordinate pair
fun getRandomLocation(): Tile { fun getRandomLocation(): Tile {
return Tile(Pair((0..tiles.value.size-1).random(), (0..tiles.value[0].size-1).random())) return Tile((0..tiles.value.size-1).random(), (0..tiles.value.get(0).size-1).random())
} }
fun setSize(x: Int, y: Int) { fun setSize(x: Int, y: Int) {
@ -45,7 +49,7 @@ object World {
//Upper line: Print each tile, print right-hand connections //Upper line: Print each tile, print right-hand connections
for (x in 0..tiles.value[0].size - 1) { for (x in 0..tiles.value[0].size - 1) {
str.append(fi) str.append(fi)
if(tiles.value[x][y].isEast()) if(tiles.value.get(x).get(y).isEast())
str.append(fi) str.append(fi)
else else
str.append(em) str.append(em)
@ -55,7 +59,7 @@ object World {
//Lower line: Print downward connections //Lower line: Print downward connections
for (x in 0..tiles.value.size - 1) { for (x in 0..tiles.value.size - 1) {
if(tiles.value[x][y].isSouth()) { if(tiles.value.get(x).get(y).isSouth()) {
str.append(fi) str.append(fi)
} }
else else

@ -1,40 +1,51 @@
package technology.zim.data package technology.zim.data
enum class Directions(val value: Pair<Int, Int>) { enum class Directions(val dir: Int) {
NORTH(Pair(0, -1)) { NONE(0),
override fun opposite(): Directions { UP(1),
return SOUTH DOWN(2),
LEFT(4),
RIGHT(8),
VISITED(16)
;
companion object {
fun getModifier(dir: Directions): Long {
return when(dir) {
UP -> NORTH
DOWN -> SOUTH
LEFT -> WEST
RIGHT -> EAST
else -> 0L
} }
},
SOUTH(Pair(0, 1)) {
override fun opposite(): Directions {
return NORTH
} }
},
EAST(Pair(1, 0)) { fun convertModifier(mod: Long): Directions {
override fun opposite(): Directions { return when(mod) {
return WEST NORTH -> UP
SOUTH -> DOWN
WEST -> LEFT
EAST -> RIGHT
else -> NONE
} }
},
WEST(Pair(-1, 0)) {
override fun opposite(): Directions {
return EAST
} }
};
//Return the opposite direction const val SOUTH: Long = 0x1L
abstract fun opposite(): Directions const val NORTH: Long = -0x1L
const val WEST: Long = -0x100000000L
fun x():Int { const val EAST: Long = 0x100000000L
return value.first val ALL = arrayOf(UP, DOWN, LEFT, RIGHT)
const val NONEMOD = 0L
} }
fun y():Int { fun inv(): Int {
return value.second return dir.inv()
} }
companion object { fun opposite(dir: Long): Long {
val ALL = setOf(NORTH, EAST, WEST, SOUTH) val x = (dir shr 32).toInt() * -1
val y = dir.toInt() * -1
return x.toLong() shl 32 + y
} }
} }

@ -2,10 +2,21 @@ package technology.zim.data
import technology.zim.World import technology.zim.World
class Tile(val value: Pair<Int, Int>): Comparable<Tile> { @JvmInline
value class Tile(val value: Long) {
constructor(x: Int, y: Int): this(Pair(x, y)) constructor(x: Int, y: Int): this(pack(x, y)) {
}
fun connect(candidateTile: Tile) {
val dir = Directions.convertModifier(this.value - candidateTile.value)
if(candidateTile.isInBounds()) {
World.update(this, getProperties().add(dir))
}
else {
throw(ArrayIndexOutOfBoundsException("Cannot connect to an out of bounds tile"))
}
}
//Connect two tiles together. //Connect two tiles together.
//Calls utility function on the connected cell //Calls utility function on the connected cell
fun connect(dir: Directions) { fun connect(dir: Directions) {
@ -14,10 +25,8 @@ class Tile(val value: Pair<Int, Int>): Comparable<Tile> {
//Ensure that the tile is within bounds //Ensure that the tile is within bounds
if(candidateTile.isInBounds()) if(candidateTile.isInBounds())
{ {
this.getProperties().add(dir) World.tiles.value[x()][y()] = getProperties().add(dir)
candidateTile.getProperties().add(dir.opposite()) World.tiles.value[candidateTile.x()][candidateTile.y()] = candidateTile.getProperties().add(dir)
candidateTile.getProperties().visited = true
candidateTile.getProperties().visited = true
} }
else { else {
//Shouldn't matter whether we skip connecting an out-of-bounds item //Shouldn't matter whether we skip connecting an out-of-bounds item
@ -39,7 +48,7 @@ class Tile(val value: Pair<Int, Int>): Comparable<Tile> {
dirs.forEach { dir -> dirs.forEach { dir ->
val candidateTile = this + dir val candidateTile = this + dir
//Ensure that the tile is within bounds //Ensure that the tile is within bounds
if(candidateTile.isInBounds() && candidateTile.getProperties().visited == explored) if(candidateTile.isInBounds() && candidateTile.getProperties().visited() == explored)
{ {
adj.add(candidateTile) adj.add(candidateTile)
} }
@ -47,95 +56,72 @@ class Tile(val value: Pair<Int, Int>): Comparable<Tile> {
return adj return adj
} }
fun getAdjacent(explored:Boolean): Set<Pair<Tile, Directions>> {
val adj = mutableSetOf<Pair<Tile, Directions>>()
val dirs = Directions.ALL
dirs.forEach { dir ->
val candidateTile = this + dir
//Ensure that the tile is within bounds
if(candidateTile.isInBounds() && candidateTile.getProperties().visited == explored)
{
adj.add(Pair(candidateTile, dir))
}
}
return adj
}
fun hasConnections(): Boolean { fun hasConnections(): Boolean {
return getProperties().connections.isNotEmpty() return getProperties().connections != 0
} }
//Gets Tile objects for all connected directions
//Used for finding a path through the maze
fun getConnections(): ArrayList<Tile> {
val listTiles = ArrayList<Tile>()
for (dir: Directions in getProperties().connections) {
//Use the ghost of linear algebra to identify potential neighbor tiles
listTiles.add(this+dir)
}
return listTiles
}
//Arguments could be made for either World or Tile knowing whether a Tile is in bounds //Arguments could be made for either World or Tile knowing whether a Tile is in bounds
fun isInBounds(): Boolean { fun isInBounds(): Boolean {
return x() >= 0 && return x() >= 0 &&
y() >= 0 && y() >= 0 &&
x() < World.tiles.value.size && x() < World.tiles.value.size &&
y() < World.tiles.value[0].size y() < World.tiles.value.get(0).size
} }
//Get the properties of the tile at the given coordinates //Get the properties of the tile at the given coordinates
fun getProperties(): TileProperties { fun getProperties(): TileProperties {
return World.tiles.value[x()][y()] return World.tiles.value.get(x()).get(y())
} }
//Get tile at given direction //Get tile at given direction
operator fun plus(dir: Directions): Tile { operator fun plus(dir: Directions): Tile {
return Tile(x() + dir.x(), y() + dir.y()) return Tile(x() + x(dir), y() + y(dir))
} }
//Get tile at direction opposite of given direction //Get tile at direction opposite of given direction
operator fun minus(dir: Directions): Tile { operator fun minus(dir: Long): Tile {
return Tile(x() - dir.x(), y() - dir.y()) return Tile(x() - x(dir), y() - y(dir))
} }
fun x(): Int { fun x(): Int {
return value.first return (value shr 32).toInt()
} }
fun y():Int { //Gets the x value of the given coordinate form
return value.second fun x(coord: Long):Int {
return (coord shr 32).toInt()
} }
override fun toString():String { //Gets the x value of the coordinate form of the given direction
return "<" + value.first + ", " + value.second + ">" fun x(dir: Directions):Int {
return (Directions.getModifier(dir) shr 32).toInt()
} }
override fun equals(other: Any?): Boolean { fun y():Int {
if (this === other) return true return value.toInt()
if (other !is Tile) return false }
if (x() != other.x() || y() != other.y()) return false //Gets the y value of the given coordinate form
fun y(coord: Long):Int {
return coord.toInt()
}
return true //Gets the y value of the coordinate form of the given direction
fun y(dir: Directions):Int {
return y() + Directions.getModifier(dir).toInt()
} }
override fun compareTo(other: Tile): Int { override fun toString():String {
if(x() == other.x() && y() == other.y()) return "<" + x() + ", " + y() + ">"
return 0
else if(y() > other.y())
return 1
else if(x() > other.x())
return 1
else
return -1
} }
override fun hashCode(): Int { companion object {
return value.hashCode() fun pack(x: Int, y: Int):Long {
return (x.toLong() shl 32) + y.toLong()
}
} }
} }

@ -1,5 +1,8 @@
package technology.zim.data package technology.zim.data
import technology.zim.data.Directions.*
//Data holder for a Tile //Data holder for a Tile
//Should contain a mutable set of connected directions //Should contain a mutable set of connected directions
//Later, can hold jumps to other locations other such fancy features //Later, can hold jumps to other locations other such fancy features
@ -8,35 +11,53 @@ package technology.zim.data
/*@JvmInline @JvmInline
value */ value class TileProperties(val connections: Int) {
class TileProperties(val connections:MutableSet<Directions> = mutableSetOf<Directions>()) {
var visited = false fun visited(): Boolean {
return connections != 0
}
//Remove a direction from the list of connections //Remove a direction from the list of connections
fun remove(dir: Directions) { fun remove(dir: Directions): TileProperties {
connections.remove(dir) return TileProperties(connections and(dir.inv()))
} }
//Add a direction to the list of connections //Add a direction to the list of connections
//Should only be accessed by the Tile class //Should only be accessed by the Tile class
fun add(dir: Directions) { fun add(dir: Directions): TileProperties {
connections.add(dir) return TileProperties(connections or(dir.dir))
} }
fun isWest(): Boolean { fun isWest(): Boolean {
return connections.contains(Directions.WEST) return connections and(LEFT.dir) != 0
} }
fun isEast():Boolean { fun isEast():Boolean {
return connections.contains(Directions.EAST) return connections and(RIGHT.dir) != 0
} }
fun isNorth(): Boolean { fun isNorth(): Boolean {
return connections.contains(Directions.NORTH) return connections and(UP.dir) != 0
} }
fun isSouth():Boolean { fun isSouth():Boolean {
return connections.contains(Directions.SOUTH) return connections and(DOWN.dir) != 0
}
infix fun and(dir: Directions): TileProperties {
return TileProperties(connections and(dir.dir))
}
infix fun and(dir: Int): TileProperties {
return TileProperties(connections and(dir))
} }
infix fun or(dir: Directions): TileProperties {
return TileProperties(connections or(dir.dir))
}
infix fun or(dir: Int): TileProperties {
return TileProperties(connections or(dir))
}
} }

@ -2,7 +2,6 @@ package technology.zim.data
//Data structure wrapper for a set of tiles //Data structure wrapper for a set of tiles
@JvmInline @JvmInline
value class WorldData(val value: ArrayList<ArrayList<TileProperties>>) { value class WorldData(val value: ArrayList<ArrayList<TileProperties>>) {
fun setSize(xmin : Int, ymin : Int) { fun setSize(xmin : Int, ymin : Int) {
@ -17,10 +16,14 @@ value class WorldData(val value: ArrayList<ArrayList<TileProperties>>) {
//Fill every row with TileProperties //Fill every row with TileProperties
for(x in value) { for(x in value) {
for(i in 0..ymin-1) { for(i in 0..ymin-1) {
x.add(TileProperties()) x.add(emptyTile)
} }
} }
//println("WorldData now sized at: " + value.size + ", " + value[0].size) //println("WorldData now sized at: " + value.size + ", " + value[0].size)
} }
companion object {
val emptyTile = TileProperties(0)
}
} }

@ -15,10 +15,10 @@ class TileTest {
} }
@Test @Test
fun connectBottomToSouthInvalid() { fun connectOutOfBoundsInvalid() {
val bottom = Tile(1, 9) val bottom = Tile(1, 9)
bottom.connect(SOUTH) bottom.connect(DOWN)
assertFalse(bottom.getConnections().contains(bottom+SOUTH)) assertFalse(bottom.getProperties().isSouth())
} }
@Test @Test
@ -40,18 +40,13 @@ class TileTest {
assertFalse(tooSouth.isInBounds()) assertFalse(tooSouth.isInBounds())
} }
@Test
fun allDirectionsTest() {
val dirs = NORTH.all()
assert(dirs.containsAll(listOf(NORTH, EAST, SOUTH, WEST)))
}
@Test @Test
fun connectNorthSouthTest() { fun connectNorthSouthTest() {
//Assumes the world is at least 10, 10 in size as set up by this test class //Assumes the world is at least 10, 10 in size as set up by this test class
val someTile = Tile(5, 5) val someTile = Tile(5, 5)
someTile.connect(NORTH) someTile.connect(UP)
val northTile = someTile + NORTH val northTile = someTile + UP
assert(someTile.getProperties().isNorth()) assert(someTile.getProperties().isNorth())
assertFalse(someTile.getProperties().isSouth()) assertFalse(someTile.getProperties().isSouth())
@ -67,16 +62,16 @@ class TileTest {
@Test @Test
fun adjacentTest() { fun adjacentTest() {
val someTile = Tile(1, 1) val someTile = Tile(1, 1)
val northTile = someTile + NORTH val northTile = someTile + UP
val southTile = someTile + SOUTH val southTile = someTile + DOWN
val westTile = someTile + WEST val westTile = someTile + LEFT
val eastTile = someTile + EAST val eastTile = someTile + RIGHT
//Generating raw tiles without built-in bounds checking, do it here //Generating raw tiles without built-in bounds checking, do it here
if(northTile.isInBounds()) if(northTile.isInBounds())
northTile.getProperties().visited = true northTile.getProperties().visited()
if(southTile.isInBounds()) if(southTile.isInBounds())
southTile.getProperties().visited = true southTile.getProperties().visited()
val adjacent = someTile.getAdjacentTiles(false) val adjacent = someTile.getAdjacentTiles(false)

Loading…
Cancel
Save