2023-12-27 14:07:32 -07:00
|
|
|
package aoc
|
|
|
|
|
|
|
|
import (
|
2024-01-09 13:53:30 -07:00
|
|
|
"math/bits"
|
2023-12-27 14:07:32 -07:00
|
|
|
"sort"
|
|
|
|
)
|
|
|
|
|
2024-01-01 10:59:40 -07:00
|
|
|
type priorityQueue[T any] struct {
|
2024-01-09 13:53:30 -07:00
|
|
|
elems []*T
|
|
|
|
less func(a, b *T) bool
|
2024-01-01 09:26:31 -07:00
|
|
|
maxDepth int
|
|
|
|
totalEnqueue int
|
2024-01-01 10:59:40 -07:00
|
|
|
totalDequeue int
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
|
|
|
|
2024-01-01 10:59:40 -07:00
|
|
|
// PriorityQueue implements a simple slice based queue.
|
|
|
|
// less is the function for sorting. reverse a and b to reverse the sort.
|
|
|
|
// T is the item
|
|
|
|
// U is a slice of T
|
2024-01-09 13:53:30 -07:00
|
|
|
func PriorityQueue[T any](less func(a, b *T) bool) *priorityQueue[T] {
|
2024-01-01 10:59:40 -07:00
|
|
|
return &priorityQueue[T]{less: less}
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
2024-01-09 13:53:30 -07:00
|
|
|
func (pq *priorityQueue[T]) Insert(elem *T) {
|
2024-01-01 09:26:31 -07:00
|
|
|
pq.totalEnqueue++
|
2024-01-01 10:59:40 -07:00
|
|
|
|
|
|
|
pq.elems = append(pq.elems, elem)
|
2024-01-01 09:26:31 -07:00
|
|
|
pq.maxDepth = max(pq.maxDepth, len(pq.elems))
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
2024-01-01 10:59:40 -07:00
|
|
|
func (pq *priorityQueue[T]) IsEmpty() bool {
|
2023-12-28 18:57:22 -07:00
|
|
|
return len(pq.elems) == 0
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
2024-01-09 13:53:30 -07:00
|
|
|
func (pq *priorityQueue[T]) ExtractMin() *T {
|
2024-01-01 10:59:40 -07:00
|
|
|
pq.totalDequeue++
|
|
|
|
|
2024-01-09 13:53:30 -07:00
|
|
|
var elem *T
|
2023-12-27 14:07:32 -07:00
|
|
|
if pq.IsEmpty() {
|
2024-01-09 13:53:30 -07:00
|
|
|
return elem
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
|
|
|
|
2024-01-09 13:53:30 -07:00
|
|
|
sort.Slice(pq.elems, func(i, j int) bool { return pq.less(pq.elems[j], pq.elems[i]) })
|
2023-12-28 18:57:22 -07:00
|
|
|
pq.elems, elem = pq.elems[:len(pq.elems)-1], pq.elems[len(pq.elems)-1]
|
2024-01-09 13:53:30 -07:00
|
|
|
return elem
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
|
|
|
|
2024-01-02 17:02:12 -07:00
|
|
|
type stack[T any] []T
|
|
|
|
|
|
|
|
func Stack[T any](a ...T) *stack[T] {
|
|
|
|
var s stack[T] = a
|
|
|
|
return &s
|
|
|
|
}
|
|
|
|
func (s *stack[T]) Push(a ...T) {
|
|
|
|
if s == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
*s = append(*s, a...)
|
|
|
|
}
|
|
|
|
func (s *stack[T]) IsEmpty() bool {
|
|
|
|
return s == nil || len(*s) == 0
|
|
|
|
}
|
|
|
|
func (s *stack[T]) Pop() T {
|
|
|
|
var a T
|
|
|
|
if s.IsEmpty() {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
a, *s = (*s)[len(*s)-1], (*s)[:len(*s)-1]
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2024-01-01 10:59:40 -07:00
|
|
|
// ManhattanDistance the distance between two points measured along axes at right angles.
|
2024-01-01 09:26:31 -07:00
|
|
|
func ManhattanDistance[T integer](a, b Point[T]) T {
|
2024-01-04 17:14:46 -07:00
|
|
|
return ABS(a[0]-b[0]) + ABS(a[1]-b[1])
|
2024-01-01 09:26:31 -07:00
|
|
|
}
|
|
|
|
|
2024-01-01 10:59:40 -07:00
|
|
|
type pather[C number, N comparable] interface {
|
2024-01-04 17:14:46 -07:00
|
|
|
// Neighbors returns all neighbors to node N that should be considered next.
|
2024-01-01 09:26:31 -07:00
|
|
|
Neighbors(N) []N
|
2024-01-04 17:14:46 -07:00
|
|
|
|
2024-01-09 13:53:30 -07:00
|
|
|
// Cost returns
|
2024-01-01 09:26:31 -07:00
|
|
|
Cost(a, b N) C
|
|
|
|
|
2024-01-04 17:14:46 -07:00
|
|
|
// Target returns true when target reached. receives node and cost.
|
|
|
|
Target(N, C) bool
|
|
|
|
|
2024-01-01 09:57:08 -07:00
|
|
|
// OPTIONAL:
|
2024-01-02 20:57:02 -07:00
|
|
|
// Add heuristic for running as A* search.
|
2024-01-04 17:14:46 -07:00
|
|
|
// Potential(N) C
|
2024-01-02 20:57:02 -07:00
|
|
|
|
2024-01-01 09:57:08 -07:00
|
|
|
// Seen modify value used by seen pruning.
|
2024-01-01 09:26:31 -07:00
|
|
|
// Seen(N) N
|
2024-01-01 10:59:40 -07:00
|
|
|
|
2024-01-01 09:26:31 -07:00
|
|
|
}
|
|
|
|
|
2024-01-01 10:59:40 -07:00
|
|
|
// FindPath uses the A* path finding algorithem.
|
|
|
|
// g is the graph source that implements the pather interface.
|
2024-01-01 14:12:53 -07:00
|
|
|
//
|
|
|
|
// C is an numeric type for calculating cost/potential
|
|
|
|
// N is the node values. is comparable for storing in visited table for pruning.
|
|
|
|
//
|
2024-01-01 10:59:40 -07:00
|
|
|
// start, end are nodes that dileniate the start and end of the search path.
|
|
|
|
// The returned values are the calculated cost and the path taken from start to end.
|
2024-01-04 17:14:46 -07:00
|
|
|
func FindPath[C integer, N comparable](g pather[C, N], start, end N) (C, []N, map[N]C) {
|
2024-01-01 09:26:31 -07:00
|
|
|
var zero C
|
2024-01-04 17:14:46 -07:00
|
|
|
|
|
|
|
var seenFn = func(a N) N { return a }
|
|
|
|
if s, ok := g.(interface{ Seen(N) N }); ok {
|
|
|
|
seenFn = s.Seen
|
|
|
|
}
|
|
|
|
|
|
|
|
var potentialFn = func(N) C { var zero C; return zero }
|
|
|
|
if p, ok := g.(interface{ Potential(N) C }); ok {
|
|
|
|
potentialFn = p.Potential
|
|
|
|
}
|
2024-01-01 09:26:31 -07:00
|
|
|
|
|
|
|
type node struct {
|
|
|
|
cost C
|
|
|
|
potential C
|
|
|
|
parent *node
|
2024-01-01 09:57:08 -07:00
|
|
|
position N
|
2024-01-01 09:26:31 -07:00
|
|
|
}
|
|
|
|
|
2024-01-04 17:14:46 -07:00
|
|
|
newPath := func(n *node) []N {
|
2024-01-01 09:26:31 -07:00
|
|
|
var path []N
|
|
|
|
for n.parent != nil {
|
2024-01-01 09:57:08 -07:00
|
|
|
path = append(path, n.position)
|
2024-01-01 09:26:31 -07:00
|
|
|
n = n.parent
|
|
|
|
}
|
2024-01-01 09:57:08 -07:00
|
|
|
path = append(path, n.position)
|
2024-01-01 09:26:31 -07:00
|
|
|
|
|
|
|
Reverse(path)
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2024-01-09 13:53:30 -07:00
|
|
|
less := func(a, b *node) bool {
|
|
|
|
return a.cost+a.potential < b.cost+b.potential
|
2024-01-02 20:57:02 -07:00
|
|
|
}
|
|
|
|
|
2024-01-04 17:14:46 -07:00
|
|
|
closed := make(map[N]C)
|
2024-01-09 13:53:30 -07:00
|
|
|
open := FibHeap(less)
|
2024-01-04 17:14:46 -07:00
|
|
|
|
2024-01-09 13:53:30 -07:00
|
|
|
open.Insert(&node{position: start, potential: potentialFn(start)})
|
2024-01-04 17:14:46 -07:00
|
|
|
closed[start] = zero
|
|
|
|
|
|
|
|
for !open.IsEmpty() {
|
2024-01-09 13:53:30 -07:00
|
|
|
current := open.ExtractMin()
|
2024-01-04 17:14:46 -07:00
|
|
|
for _, nb := range g.Neighbors(current.position) {
|
2024-01-09 13:53:30 -07:00
|
|
|
next := &node{
|
2024-01-01 09:57:08 -07:00
|
|
|
position: nb,
|
2024-01-09 13:53:30 -07:00
|
|
|
parent: current,
|
2024-01-04 17:14:46 -07:00
|
|
|
cost: g.Cost(current.position, nb) + current.cost,
|
|
|
|
potential: potentialFn(nb),
|
2024-01-01 09:26:31 -07:00
|
|
|
}
|
2024-01-04 17:14:46 -07:00
|
|
|
|
|
|
|
seen := seenFn(nb)
|
|
|
|
cost, ok := closed[seen]
|
|
|
|
if !ok || next.cost < cost {
|
2024-01-09 13:53:30 -07:00
|
|
|
open.Insert(next)
|
2024-01-04 17:14:46 -07:00
|
|
|
closed[seen] = next.cost
|
2024-01-09 13:53:30 -07:00
|
|
|
}
|
2024-01-04 17:14:46 -07:00
|
|
|
|
|
|
|
if next.potential == zero && g.Target(next.position, next.cost) {
|
2024-01-09 13:53:30 -07:00
|
|
|
return next.cost, newPath(next), closed
|
2024-01-01 14:12:53 -07:00
|
|
|
}
|
2024-01-01 09:26:31 -07:00
|
|
|
}
|
|
|
|
}
|
2024-01-04 17:14:46 -07:00
|
|
|
return zero, nil, closed
|
2023-12-27 14:07:32 -07:00
|
|
|
}
|
2024-01-09 13:53:30 -07:00
|
|
|
|
|
|
|
type fibTree[T any] struct {
|
|
|
|
value *T
|
|
|
|
parent *fibTree[T]
|
|
|
|
child []*fibTree[T]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *fibTree[T]) addAtEnd(n *fibTree[T]) {
|
|
|
|
n.parent = t
|
|
|
|
t.child = append(t.child, n)
|
|
|
|
}
|
|
|
|
|
|
|
|
type fibHeap[T any] struct {
|
|
|
|
trees []*fibTree[T]
|
|
|
|
least *fibTree[T]
|
|
|
|
count uint
|
|
|
|
less func(a, b *T) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func FibHeap[T any](less func(a, b *T) bool) *fibHeap[T] {
|
|
|
|
return &fibHeap[T]{less: less}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *fibHeap[T]) GetMin() *T {
|
|
|
|
return h.least.value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *fibHeap[T]) IsEmpty() bool { return h.least == nil }
|
|
|
|
|
|
|
|
func (h *fibHeap[T]) Insert(v *T) {
|
|
|
|
ntree := &fibTree[T]{value: v}
|
|
|
|
h.trees = append(h.trees, ntree)
|
|
|
|
if h.least == nil || h.less(v, h.least.value) {
|
|
|
|
h.least = ntree
|
|
|
|
}
|
|
|
|
h.count++
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *fibHeap[T]) ExtractMin() *T {
|
|
|
|
smallest := h.least
|
|
|
|
if smallest != nil {
|
|
|
|
// Remove smallest from root trees.
|
|
|
|
for i := range h.trees {
|
|
|
|
pos := h.trees[i]
|
|
|
|
if pos == smallest {
|
|
|
|
h.trees[i] = h.trees[len(h.trees)-1]
|
|
|
|
h.trees = h.trees[:len(h.trees)-1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add children to root
|
|
|
|
h.trees = append(h.trees, smallest.child...)
|
|
|
|
smallest.child = smallest.child[:0]
|
|
|
|
|
|
|
|
h.least = nil
|
|
|
|
if len(h.trees) > 0 {
|
|
|
|
h.consolidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
h.count--
|
|
|
|
return smallest.value
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *fibHeap[T]) consolidate() {
|
|
|
|
aux := make([]*fibTree[T], bits.Len(h.count))
|
|
|
|
for _, x := range h.trees {
|
|
|
|
order := len(x.child)
|
|
|
|
|
|
|
|
// consolidate the larger roots under smaller roots of same order until we have at most one tree per order.
|
|
|
|
for aux[order] != nil {
|
|
|
|
y := aux[order]
|
|
|
|
if h.less(y.value, x.value) {
|
|
|
|
x, y = y, x
|
|
|
|
}
|
|
|
|
x.addAtEnd(y)
|
|
|
|
aux[order] = nil
|
|
|
|
order++
|
|
|
|
}
|
|
|
|
aux[order] = x
|
|
|
|
}
|
|
|
|
|
|
|
|
h.trees = h.trees[:0]
|
|
|
|
// move ordered trees to root and find least node.
|
|
|
|
for _, k := range aux {
|
|
|
|
if k != nil {
|
|
|
|
k.parent = nil
|
|
|
|
h.trees = append(h.trees, k)
|
|
|
|
if h.least == nil || h.less(k.value, h.least.value) {
|
|
|
|
h.least = k
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *fibHeap[T]) Merge(a *fibHeap[T]) {
|
|
|
|
h.trees = append(h.trees, a.trees...)
|
|
|
|
h.count += a.count
|
|
|
|
h.consolidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
// func (h *fibHeap[T]) Find(n *T) *fibTree[T] {
|
|
|
|
|
|
|
|
// }
|