Optimizations and minor cleanups

Thanks to Go's profiling tools, I discovered that the memoization, while it was cutting down runtime significantly, was itself slow because it was using arrays. Swapping those arrays out for maps made a _massive_ difference (4s/14s part1/part2 to 1ms/2ms with no other changes). Lesson learned. Again.

The IntHeap rename was long overdue since I took the code originally from Go's sample docs for priority queues.
This commit is contained in:
2022-06-18 22:47:30 -05:00
parent 267146ed8e
commit 68ca26e3aa

View File

@ -106,7 +106,7 @@ func (d Day18) findAdjacentCells(inPos day18Vec, keys, doors map[day18Vec]int, g
} }
queue := deque.NewDeque[u.Pair[int, day18Vec]]() queue := deque.NewDeque[u.Pair[int, day18Vec]]()
visited := []day18Vec{inPos} visited := make(map[day18Vec]bool)
for _, adjacent := range getAdjacent(inPos) { for _, adjacent := range getAdjacent(inPos) {
queue.PushBack(u.Pair[int, day18Vec]{First: 1, Second: adjacent}) queue.PushBack(u.Pair[int, day18Vec]{First: 1, Second: adjacent})
} }
@ -114,8 +114,8 @@ func (d Day18) findAdjacentCells(inPos day18Vec, keys, doors map[day18Vec]int, g
for !queue.IsEmpty() { for !queue.IsEmpty() {
next := queue.PopFront() next := queue.PopFront()
if !u.ArrayContains(visited, next.Second) { if _, exists := visited[next.Second]; !exists {
visited = append(visited, next.Second) visited[next.Second] = true
key, adjacentIsKey := keys[next.Second] key, adjacentIsKey := keys[next.Second]
door, adjacentIsDoor := doors[next.Second] door, adjacentIsDoor := doors[next.Second]
@ -141,7 +141,7 @@ func (d Day18) findAdjacentCells(inPos day18Vec, keys, doors map[day18Vec]int, g
} }
for _, neighbor := range getAdjacent(next.Second) { for _, neighbor := range getAdjacent(next.Second) {
if !u.ArrayContains(visited, neighbor) { if _, exists := visited[neighbor]; !exists {
queue.PushBack(u.Pair[int, day18Vec]{First: next.First + 1, Second: neighbor}) queue.PushBack(u.Pair[int, day18Vec]{First: next.First + 1, Second: neighbor})
} }
} }
@ -155,19 +155,17 @@ type day18PriorityQueue struct {
distance int distance int
neighbor rune neighbor rune
} }
type IntHeap []day18PriorityQueue type PriorityQueueHeap []day18PriorityQueue
func (h IntHeap) Len() int { return len(h) } func (h PriorityQueueHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i].distance < h[j].distance } func (h PriorityQueueHeap) Less(i, j int) bool { return h[i].distance < h[j].distance }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h PriorityQueueHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x any) { func (h *PriorityQueueHeap) Push(x any) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*h = append(*h, x.(day18PriorityQueue)) *h = append(*h, x.(day18PriorityQueue))
} }
func (h *IntHeap) Pop() any { func (h *PriorityQueueHeap) Pop() any {
old := *h old := *h
n := len(old) n := len(old)
x := old[n-1] x := old[n-1]
@ -176,25 +174,25 @@ func (h *IntHeap) Pop() any {
} }
type reachableKeysMemo struct { type reachableKeysMemo struct {
pos rune pos rune
keysFound int keysFound int
reachableKeys []u.Pair[rune, int]
} }
var knownReachableKeys = make([]reachableKeysMemo, 0) var knownReachableKeys = make(map[reachableKeysMemo][]u.Pair[rune, int])
func (d Day18) reachableKeys(inPos rune, keysFound int, graph day18Graph) []u.Pair[rune, int] { func (d Day18) reachableKeys(inPos rune, keysFound int, graph day18Graph) []u.Pair[rune, int] {
for _, m := range knownReachableKeys { memo := reachableKeysMemo{
if m.pos == inPos && pos: inPos,
m.keysFound == keysFound { keysFound: keysFound,
return m.reachableKeys }
} if v, exists := knownReachableKeys[memo]; exists {
return v
} }
ret := make([]u.Pair[rune, int], 0) ret := make([]u.Pair[rune, int], 0)
distance := make(map[rune]int) distance := make(map[rune]int)
ih := make(IntHeap, 0) ih := make(PriorityQueueHeap, 0)
for _, p := range graph[inPos] { for _, p := range graph[inPos] {
ih = append(ih, day18PriorityQueue{ ih = append(ih, day18PriorityQueue{
@ -231,31 +229,26 @@ func (d Day18) reachableKeys(inPos rune, keysFound int, graph day18Graph) []u.Pa
} }
} }
memo := reachableKeysMemo{ knownReachableKeys[memo] = ret
pos: inPos, return ret
keysFound: keysFound,
reachableKeys: ret,
}
knownReachableKeys = append(knownReachableKeys, memo)
return memo.reachableKeys
} }
type minStepsMemo struct { type minStepsMemo struct {
pos string pos string
keysToFind int keysToFind int
keysFound int keysFound int
dist int
} }
var knownMinimumSteps = make([]minStepsMemo, 0) var knownMinimumSteps = make(map[minStepsMemo]int, 0)
func (d Day18) minimumSteps(inPos string, keysToFind int, keysFound int, graph day18Graph) int { func (d Day18) minimumSteps(inPos string, keysToFind int, keysFound int, graph day18Graph) int {
for _, m := range knownMinimumSteps { memo := minStepsMemo{
if m.pos == inPos && pos: inPos,
m.keysToFind == keysToFind && keysToFind: keysToFind,
m.keysFound == keysFound { keysFound: keysFound,
return m.dist }
} if v, exists := knownMinimumSteps[memo]; exists {
return v
} }
if keysToFind == 0 { if keysToFind == 0 {
@ -285,14 +278,8 @@ func (d Day18) minimumSteps(inPos string, keysToFind int, keysFound int, graph d
} }
} }
memo := minStepsMemo{ knownMinimumSteps[memo] = int(best)
pos: inPos, return int(best)
keysToFind: keysToFind,
keysFound: keysFound,
dist: int(best),
}
knownMinimumSteps = append(knownMinimumSteps, memo)
return memo.dist
} }
func (d Day18) buildGraph(pos []day18Vec, keys map[day18Vec]int, doors map[day18Vec]int, grid [][]day18Cell) day18Graph { func (d Day18) buildGraph(pos []day18Vec, keys map[day18Vec]int, doors map[day18Vec]int, grid [][]day18Cell) day18Graph {
@ -347,8 +334,9 @@ func (d *Day18) Part2() string {
entrances := d.part2PatchMap(grid, d.entrance) entrances := d.part2PatchMap(grid, d.entrance)
// d.Draw(grid, d.keys, d.doors, entrances...) // d.Draw(grid, d.keys, d.doors, entrances...)
knownMinimumSteps = make([]minStepsMemo, 0) // clear memoized maps that (might have) came from part1
knownReachableKeys = make([]reachableKeysMemo, 0) knownMinimumSteps = make(map[minStepsMemo]int)
knownReachableKeys = make(map[reachableKeysMemo][]u.Pair[rune, int])
graph := d.buildGraph(entrances, d.keys, d.doors, grid) graph := d.buildGraph(entrances, d.keys, d.doors, grid)
minSteps := d.minimumSteps("1234", len(d.keys), 0, graph) minSteps := d.minimumSteps("1234", len(d.keys), 0, graph)