From 22184ed9c7bb6f32f558f6c3b40f02b44705d1fc Mon Sep 17 00:00:00 2001 From: xuu Date: Tue, 2 Jan 2024 17:02:12 -0700 Subject: [PATCH] chore(aoc): initial graph attempt --- aoc_test.go | 39 +++++++++++++++++ grids.go | 102 +++++++++++++++++++++++++++++++++++++++++-- search.go | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 3 deletions(-) diff --git a/aoc_test.go b/aoc_test.go index 7e9d5a1..09dfd25 100644 --- a/aoc_test.go +++ b/aoc_test.go @@ -195,3 +195,42 @@ func ExamplePriorityQueue() { // point 5 is 22 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) +} \ No newline at end of file diff --git a/grids.go b/grids.go index 22df4ed..b8227f4 100644 --- a/grids.go +++ b/grids.go @@ -1,5 +1,10 @@ package aoc +import ( + "cmp" + "sort" +) + type Vector struct { Offset Point[int] Scale int @@ -58,7 +63,7 @@ func NumPoints(outline []Point[int], borderLength int) int { 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 if !m.Valid(p) { 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 } -func (m *Map[I,T]) Size() (I, I) { +func (m *Map[I, T]) Size() (I, I) { if m == nil || len(*m) == 0 { return 0, 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() 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() diff --git a/search.go b/search.go index a559129..fc305b2 100644 --- a/search.go +++ b/search.go @@ -1,6 +1,7 @@ package aoc import ( + "maps" "sort" ) @@ -41,6 +42,30 @@ func (pq *priorityQueue[T]) Dequeue() (T, bool) { 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. func ManhattanDistance[T integer](a, b Point[T]) T { 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 } + +// 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 +}