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:
82
days/18.go
82
days/18.go
@ -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)
|
||||||
|
Reference in New Issue
Block a user