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