chore(aoc): initial graph attempt
This commit is contained in:
parent
74a952e82b
commit
22184ed9c7
39
aoc_test.go
39
aoc_test.go
|
@ -195,3 +195,42 @@ func ExamplePriorityQueue() {
|
||||||
// point 5 is 22 steps away.
|
// point 5 is 22 steps away.
|
||||||
// point 6 is 19 steps away.
|
// point 6 is 19 steps away.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStack(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
s := aoc.Stack(1,2,3,4)
|
||||||
|
is.True(!s.IsEmpty())
|
||||||
|
is.Equal(s.Pop(), 4)
|
||||||
|
is.Equal(s.Pop(), 3)
|
||||||
|
is.Equal(s.Pop(), 2)
|
||||||
|
is.Equal(s.Pop(), 1)
|
||||||
|
is.True(s.IsEmpty())
|
||||||
|
s.Push(4,3,2,1)
|
||||||
|
is.True(!s.IsEmpty())
|
||||||
|
is.Equal(s.Pop(), 1)
|
||||||
|
is.Equal(s.Pop(), 2)
|
||||||
|
is.Equal(s.Pop(), 3)
|
||||||
|
is.Equal(s.Pop(), 4)
|
||||||
|
is.True(s.IsEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraph(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
var adjacencyList = map[int][]int{
|
||||||
|
2: {3, 5, 1},
|
||||||
|
1: {2, 4},
|
||||||
|
3: {6, 2},
|
||||||
|
4: {1, 5, 7},
|
||||||
|
5: {2, 6, 8, 4},
|
||||||
|
6: {3, 0, 9, 5},
|
||||||
|
7: {4, 8},
|
||||||
|
8: {5, 9, 7},
|
||||||
|
9: {6, 0, 8},
|
||||||
|
}
|
||||||
|
|
||||||
|
g := aoc.Graph(aoc.WithAdjacencyList[int,int](adjacencyList))
|
||||||
|
is.Equal(g.Neighbors(1), []int{2,4})
|
||||||
|
is.Equal(map[int][]int(g.AdjacencyList()), adjacencyList)
|
||||||
|
}
|
102
grids.go
102
grids.go
|
@ -1,5 +1,10 @@
|
||||||
package aoc
|
package aoc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
type Vector struct {
|
type Vector struct {
|
||||||
Offset Point[int]
|
Offset Point[int]
|
||||||
Scale int
|
Scale int
|
||||||
|
@ -58,7 +63,7 @@ func NumPoints(outline []Point[int], borderLength int) int {
|
||||||
|
|
||||||
type Map[I integer, T any] [][]T
|
type Map[I integer, T any] [][]T
|
||||||
|
|
||||||
func (m *Map[I,T]) Get(p Point[I]) (Point[I], T, bool) {
|
func (m *Map[I, T]) Get(p Point[I]) (Point[I], T, bool) {
|
||||||
var zero T
|
var zero T
|
||||||
if !m.Valid(p) {
|
if !m.Valid(p) {
|
||||||
return [2]I{0, 0}, zero, false
|
return [2]I{0, 0}, zero, false
|
||||||
|
@ -66,13 +71,104 @@ func (m *Map[I,T]) Get(p Point[I]) (Point[I], T, bool) {
|
||||||
|
|
||||||
return p, (*m)[p[0]][p[1]], true
|
return p, (*m)[p[0]][p[1]], true
|
||||||
}
|
}
|
||||||
func (m *Map[I,T]) Size() (I, I) {
|
func (m *Map[I, T]) Size() (I, I) {
|
||||||
if m == nil || len(*m) == 0 {
|
if m == nil || len(*m) == 0 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
return I(len(*m)), I(len((*m)[0]))
|
return I(len(*m)), I(len((*m)[0]))
|
||||||
}
|
}
|
||||||
func (m *Map[I,T]) Valid(p Point[I]) bool {
|
func (m *Map[I, T]) Valid(p Point[I]) bool {
|
||||||
rows, cols := m.Size()
|
rows, cols := m.Size()
|
||||||
return p[0] >= 0 && p[0] < rows && p[1] >= 0 && p[1] < cols
|
return p[0] >= 0 && p[0] < rows && p[1] >= 0 && p[1] < cols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type adjacencyList[V any, C comparable] map[C][]V
|
||||||
|
type graph[V any, W cmp.Ordered, C comparable] map[C]*vertex[V, W]
|
||||||
|
type graphOption[V any, W cmp.Ordered, C comparable] func(g *graph[V, W, C])
|
||||||
|
type vertex[V any, W cmp.Ordered] struct {
|
||||||
|
Value V
|
||||||
|
Edges edges[V, W]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vertex[V, W]) Neighbors() []V {
|
||||||
|
var nbs []V
|
||||||
|
sort.Sort(v.Edges)
|
||||||
|
for _, e := range v.Edges {
|
||||||
|
nbs = append(nbs, e.Vertex.Value)
|
||||||
|
}
|
||||||
|
return nbs
|
||||||
|
}
|
||||||
|
|
||||||
|
type edge[V any, W cmp.Ordered] struct {
|
||||||
|
Vertex *vertex[V, W]
|
||||||
|
Weight W
|
||||||
|
}
|
||||||
|
type edges[V any, W cmp.Ordered] []edge[V, W]
|
||||||
|
|
||||||
|
func (e edges[V, W]) Len() int { return len(e) }
|
||||||
|
func (e edges[V, W]) Less(i, j int) bool { return e[i].Weight < e[j].Weight }
|
||||||
|
func (e edges[V, W]) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||||
|
|
||||||
|
func Graph[V any, W cmp.Ordered, C comparable](opts ...graphOption[V, W, C]) *graph[V, W, C] {
|
||||||
|
g := make(graph[V, W, C])
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&g)
|
||||||
|
}
|
||||||
|
return &g
|
||||||
|
}
|
||||||
|
func (g *graph[V, W, C]) AddVertex(id C, value V) {
|
||||||
|
(*g)[id] = &vertex[V,W]{Value: value}
|
||||||
|
}
|
||||||
|
func (g *graph[V, W, C]) AddEdge(from, to C, w W) {
|
||||||
|
if g == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := (*g)[from]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := (*g)[to]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
(*g)[from].Edges = append((*g)[from].Edges, edge[V,W]{(*g)[to], w})
|
||||||
|
}
|
||||||
|
func (g *graph[V, W, C]) Neighbors(v C) []V {
|
||||||
|
if g == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*g)[v].Neighbors()
|
||||||
|
}
|
||||||
|
func (g *graph[V, W, C]) AdjacencyList() adjacencyList[V, C] {
|
||||||
|
m := make(map[C][]V)
|
||||||
|
for id, v := range *g {
|
||||||
|
if len(v.Edges) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[id] = v.Neighbors()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAdjacencyList[W cmp.Ordered, C comparable](list adjacencyList[C, C]) graphOption[C, W, C] {
|
||||||
|
var zeroW W
|
||||||
|
return func(g *graph[C, W, C]) {
|
||||||
|
for vertex, edges := range list {
|
||||||
|
if _, ok := (*g)[vertex]; !ok {
|
||||||
|
g.AddVertex(vertex, vertex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add edges to vertex
|
||||||
|
for _, edge := range edges {
|
||||||
|
// add edge as vertex, if not added
|
||||||
|
if _, ok := (*g)[edge]; !ok {
|
||||||
|
g.AddVertex(edge, edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.AddEdge(vertex, edge, zeroW) // no weights in this adjacency list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func GraphFromMap()
|
||||||
|
|
122
search.go
122
search.go
|
@ -1,6 +1,7 @@
|
||||||
package aoc
|
package aoc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,6 +42,30 @@ func (pq *priorityQueue[T]) Dequeue() (T, bool) {
|
||||||
return elem, true
|
return elem, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// ManhattanDistance the distance between two points measured along axes at right angles.
|
// ManhattanDistance the distance between two points measured along axes at right angles.
|
||||||
func ManhattanDistance[T integer](a, b Point[T]) T {
|
func ManhattanDistance[T integer](a, b Point[T]) T {
|
||||||
return ABS(a[1]-b[1]) + ABS(a[0]-b[0])
|
return ABS(a[1]-b[1]) + ABS(a[0]-b[0])
|
||||||
|
@ -148,3 +173,100 @@ func FindPath[C integer, N comparable](g pather[C, N], start, end N) (C, []N) {
|
||||||
}
|
}
|
||||||
return zero, nil
|
return zero, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindPath uses the A* path finding algorithem.
|
||||||
|
// g is the graph source that implements the pather interface.
|
||||||
|
//
|
||||||
|
// C is an numeric type for calculating cost/potential
|
||||||
|
// N is the node values. is comparable for storing in visited table for pruning.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
func FindPaths[C integer, N comparable](g pather[C, N], start, end N) ([]C, [][]N) {
|
||||||
|
var zero C
|
||||||
|
// closed := make(map[N]bool)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
cost C
|
||||||
|
potential C
|
||||||
|
parent *node
|
||||||
|
position N
|
||||||
|
closed map[N]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
NewPath := func(n *node) []N {
|
||||||
|
var path []N
|
||||||
|
for n.parent != nil {
|
||||||
|
path = append(path, n.position)
|
||||||
|
n = n.parent
|
||||||
|
}
|
||||||
|
path = append(path, n.position)
|
||||||
|
|
||||||
|
Reverse(path)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
less := func(b, a node) bool {
|
||||||
|
return b.cost+b.potential < a.cost+a.potential
|
||||||
|
}
|
||||||
|
|
||||||
|
pq := PriorityQueue(less)
|
||||||
|
pq.Enqueue(node{position: start, closed: make(map[N]bool)})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
Log("queue max depth = ", pq.maxDepth, "total enqueue = ", pq.totalEnqueue, "total dequeue = ", pq.totalDequeue)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var seenFn = func(a N) N { return a }
|
||||||
|
if s, ok := g.(interface{ Seen(N) N }); ok {
|
||||||
|
seenFn = s.Seen
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetFn = func(a N) bool { return true }
|
||||||
|
if s, ok := g.(interface{ Target(N) bool }); ok {
|
||||||
|
targetFn = s.Target
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths [][]N
|
||||||
|
var costs []C
|
||||||
|
|
||||||
|
for !pq.IsEmpty() {
|
||||||
|
current, _ := pq.Dequeue()
|
||||||
|
cost, potential, n := current.cost, current.potential, current.position
|
||||||
|
|
||||||
|
seen := seenFn(n)
|
||||||
|
if current.closed[seen] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
current.closed[seen] = true
|
||||||
|
|
||||||
|
if cost > 0 && potential == zero && cost > Max(0, costs...) && targetFn(current.position) {
|
||||||
|
paths = append([][]N(nil), NewPath(¤t))
|
||||||
|
costs = append([]C(nil), cost)
|
||||||
|
Log("new record = ", cost)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nb := range g.Neighbors(n) {
|
||||||
|
seen := seenFn(nb)
|
||||||
|
if current.closed[seen] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cost := g.Cost(n, nb) + current.cost
|
||||||
|
next := node{
|
||||||
|
position: nb,
|
||||||
|
parent: ¤t,
|
||||||
|
cost: cost,
|
||||||
|
potential: g.Potential(nb, end),
|
||||||
|
closed: maps.Clone(current.closed),
|
||||||
|
}
|
||||||
|
// check if path is in open list
|
||||||
|
if _, open := current.closed[seen]; !open {
|
||||||
|
next.closed[seen] = false // add to open list
|
||||||
|
pq.Enqueue(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return costs, paths
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user