Skip to content

Commit

Permalink
Cleanup and Optimization (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
peckb1 authored Dec 17, 2024
1 parent 08ad317 commit 18ab796
Showing 1 changed file with 68 additions and 101 deletions.
169 changes: 68 additions & 101 deletions src/main/kotlin/me/peckb/aoc/_2024/calendar/day16/Day16.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
import me.peckb.aoc.pathing.Dijkstra
import me.peckb.aoc.pathing.DijkstraNodeWithCost
import java.util.PriorityQueue
import kotlin.math.abs
import kotlin.math.min

class Day16 @Inject constructor(
private val generatorFactory: InputGeneratorFactory,
Expand Down Expand Up @@ -38,8 +40,8 @@ class Day16 @Inject constructor(
}

fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
lateinit var start: Pair<Int, Int>
lateinit var end: Pair<Int, Int>
lateinit var start: Location
lateinit var end: Location

val maze = mutableListOf<MutableList<Space>>()
input.forEachIndexed { y, line ->
Expand All @@ -48,107 +50,63 @@ class Day16 @Inject constructor(
when (c) {
'#' -> row.add(Space.FULL)
'.' -> row.add(Space.EMPTY)
'S' -> row.add(Space.EMPTY.also { start = y to x } )
'E' -> row.add(Space.EMPTY.also { end = y to x } )
'S' -> row.add(Space.EMPTY.also { start = Location(y, x) } )
'E' -> row.add(Space.EMPTY.also { end = Location(y, x) } )
}
}
maze.add(row)
}

val visitedWithCheapestCost = mutableMapOf<Triple<Int, Int, Direction>, Long>()
val visited = mutableSetOf<SpaceWithPath>()
val toVisit = PriorityQueue<SpaceWithPath>()

toVisit.add(SpaceWithPath(start, end, listOf(start)))
loop@ while(toVisit.isNotEmpty()) {
val current = toVisit.poll()

if (current.loc == end) {
visited.add(current)
continue@loop
}
val myDirection = SpaceWithPath.findDirection(current.path)
val key = Triple(current.loc.first, current.loc.second, myDirection)
if (visitedWithCheapestCost.getOrDefault(key, Long.MAX_VALUE) < current.cost) {
continue@loop
} else {
visitedWithCheapestCost[key] = current.cost
val visitedInSolutions = mutableSetOf<Location>()
val cheapest = mutableMapOf<Route, Long>()
val toVisit = PriorityQueue<Route> { r1, r2 ->
val d1 = abs(end.y - r1.loc.y) + abs(end.x - r1.loc.x)
val d2 = abs(end.y - r2.loc.y) + abs(end.x - r2.loc.x)
when (val n = d1.compareTo(d2)) {
0 -> r1.cost.compareTo(r2.cost)
else -> n
}
}.apply { add(Route(start, E).withPath(listOf(start))) }
var cheapestEndSolution = Long.MAX_VALUE

val n = (-1 to 0)
val e = (0 to 1)
val s = (1 to 0)
val w = (0 to -1)

listOf(n, s, e, w)
.asSequence()
.map { (yDelta, xDelta) ->
current.loc.first + yDelta to current.loc.second + xDelta
}
.filter { (y, x) ->
y in maze.indices && x in maze[y].indices && maze[y][x] == Space.EMPTY && !current.path.contains(y to x)
}
.forEach { (y, x) ->
toVisit.add(SpaceWithPath(y to x, end, current.path.plus(y to x)))
}
}

val minCostRoutes = visited.filter { it.loc == end }.groupBy { (_, _, path, cost) ->
cost
}.minBy { it.key }
search@ while(toVisit.isNotEmpty()) {
val cur = toVisit.poll()

minCostRoutes.value.fold(mutableSetOf<Pair<Int, Int>>()) { acc, next ->
acc.also { it.addAll(next.path) }
}.size
}
if (cur.cost > cheapestEndSolution) {
continue@search
}

data class SpaceWithCost(
val loc: Pair<Int, Int>,
val cost: Long
)

data class SpaceWithPath(
val loc: Pair<Int, Int>,
val end: Pair<Int, Int>,
val path: List<Pair<Int, Int>>,
val cost: Long = findCost(path)
) : Comparable<SpaceWithPath> {
override fun compareTo(other: SpaceWithPath): Int = cost.compareTo(other.cost)

companion object {
fun findDirection(path: List<Pair<Int, Int>>): Direction {
var direction = E
path.windowed(2).lastOrNull()?.let { (s, d) ->
val deltas = (d.first - s.first) to (d.second - s.second)

direction = when (deltas) {
(-1 to 0) -> N
(0 to 1) -> E
(1 to 0) -> S
(0 to -1) -> W
else -> throw IllegalStateException("Unknown Deltas: $deltas")
}
if (cur.loc == end) {
if (cur.cost < cheapestEndSolution) {
visitedInSolutions.clear()
cheapestEndSolution = min(cheapestEndSolution, cur.cost)
}
return direction
visitedInSolutions.addAll(cur.path)
continue@search
}

fun findCost(path: List<Pair<Int, Int>>): Long {
var direction = E
return path.windowed(2).sumOf { (s, d) ->
val deltas = (d.first - s.first) to (d.second - s.second)

val newDirection = when (deltas) {
(-1 to 0) -> N
(0 to 1) -> E
(1 to 0) -> S
(0 to -1) -> W
else -> throw IllegalStateException("Unknown Deltas: $deltas")
val cheapestToHere = cheapest[cur] ?: Long.MAX_VALUE
if (cheapestToHere < cur.cost) { continue@search }
cheapest[cur] = cur.cost

Direction.entries.forEach { d ->
val nextSpot = Location(cur.loc.y + d.yDelta, cur.loc.x + d.xDelta)
if (nextSpot.y in maze.indices &&
nextSpot.x in maze[nextSpot.y].indices &&
maze[nextSpot.y][nextSpot.x] == Space.EMPTY &&
!cur.path.contains(nextSpot)
) {
val extraCost = cur.direction.turnCost(d) + 1
val newCost = cur.cost + extraCost
if (newCost <= cheapestEndSolution) {
val newRoute = Route(nextSpot, d).withCost(newCost).withPath(cur.path.plus(nextSpot))
toVisit.add(newRoute)
}

(direction.turns(newDirection) + 1).also { direction = newDirection }
}
}
}

visitedInSolutions.size
}
}

Expand All @@ -168,17 +126,17 @@ data class RoomWithCost(val room: Room, val cost: Long) : DijkstraNodeWithCost<R

override fun neighbors(): List<DijkstraNodeWithCost<Room, Long>> {
val myDirection = room.direction
val n = (-1 to 0) to Direction.N
val n = (-1 to 0) to N
val e = (0 to 1) to E
val s = (1 to 0) to Direction.S
val w = (0 to -1) to Direction.W
val s = (1 to 0) to S
val w = (0 to -1) to W

return listOf(n, s, e, w).mapNotNull { (loc, dir) ->
val (y, x) = room.y + loc.first to room.x + loc.second
if (y !in maze.indices || x !in maze[y].indices) { null }
else if (maze[y][x].space == Space.FULL) { null }
else {
val cost = myDirection.turns(dir) + 1
val cost = myDirection.turnCost(dir) + 1
RoomWithCost(Room(y, x).also { it.direction = dir }, cost).withMaze(maze)
}
}
Expand All @@ -187,10 +145,6 @@ data class RoomWithCost(val room: Room, val cost: Long) : DijkstraNodeWithCost<R
override fun node(): Room = room

override fun cost(): Long = cost

companion object {
private const val TURN_COST = 1000
}
}

data class Room(
Expand All @@ -203,17 +157,30 @@ data class Room(

enum class Space { FULL, EMPTY }

enum class Direction { N, E, S, W }
enum class Direction(val yDelta: Int, val xDelta: Int) {
N(-1, 0), E(0, 1), S(1, 0), W(0, -1)
}

private fun Direction?.turns(direction: Direction): Long {
private fun Direction?.turnCost(direction: Direction): Long {
if (this == null) return 0
if (this == direction) return 0

if (this == Direction.N && direction == Direction.S ||
this == Direction.S && direction == Direction.N ||
this == E && direction == Direction.W ||
this == Direction.W && direction == E
if (
this == N && direction == S ||
this == S && direction == N ||
this == E && direction == W ||
this == W && direction == E
) return 2000

return 1000
}

data class Location(val y: Int, val x: Int)

data class Route(val loc: Location, val direction: Direction) {
var path: List<Location> = emptyList()
var cost: Long = 0

fun withPath(p: List<Location>) = apply { this.path = p }
fun withCost(c: Long) = apply { this.cost = c }
}

0 comments on commit 18ab796

Please sign in to comment.