graphs #21

Merged
xuu merged 4 commits from graphs into main 2024-01-09 13:56:12 -07:00
2 changed files with 30 additions and 113 deletions
Showing only changes of commit 924c8d74f3 - Show all commits

View File

@ -29,10 +29,10 @@ func run(scan *bufio.Scanner) (*result, error) {
log("start day 17") log("start day 17")
result := result{} result := result{}
result.valuePT1 = search(m, 1, 3) result.valuePT1 = search(m, 1, 3, seenFn)
log("result from part 1 = ", result.valuePT1) log("result from part 1 = ", result.valuePT1)
result.valuePT2 = search(m, 4, 10) result.valuePT2 = search(m, 4, 10, nil)
log("result from part 2 = ", result.valuePT2) log("result from part 2 = ", result.valuePT2)
return &result, nil return &result, nil
@ -90,6 +90,7 @@ type graph struct {
m Map m Map
target Point target Point
reads int reads int
seenFn func(a position) position
} }
// Neighbors returns valid steps from given position. if at target returns none. // Neighbors returns valid steps from given position. if at target returns none.
@ -129,9 +130,9 @@ func (g *graph) Cost(a, b position) int16 {
} }
// Potential calculates distance to target // Potential calculates distance to target
func (g *graph) Potential(a, b position) int16 { // func (g *graph) Potential(a, b position) int16 {
return aoc.ManhattanDistance(a.loc, b.loc) // return aoc.ManhattanDistance(a.loc, b.loc)
} // }
func (g *graph) Target(a position) bool { func (g *graph) Target(a position) bool {
if a.loc == g.target && a.steps >= g.min { if a.loc == g.target && a.steps >= g.min {
@ -143,22 +144,29 @@ func (g *graph) Target(a position) bool {
// Seen attempt at simplifying the seen to use horizontal/vertical and no steps. // Seen attempt at simplifying the seen to use horizontal/vertical and no steps.
// It returns correct for part1 but not part 2.. // It returns correct for part1 but not part 2..
// func (g *graph) Seen(a position) position { // func (g *graph) Seen(a position) position {
// if a.direction == U { // if g.seenFn != nil {
// a.direction = D // return g.seenFn(a)
// } // }
// if a.direction == L {
// a.direction = R
// }
// a.steps = 0
// return a // return a
// } // }
func search(m Map, minSteps, maxSteps int8) int { func seenFn(a position) position {
if a.direction == U {
a.direction = D
}
if a.direction == L {
a.direction = R
}
a.steps = 0
return a
}
func search(m Map, minSteps, maxSteps int8, seenFn func(position) position) int {
rows, cols := m.Size() rows, cols := m.Size()
start := Point{} start := Point{}
target := Point{rows - 1, cols - 1} target := Point{rows - 1, cols - 1}
g := graph{min: minSteps, max: maxSteps, m: m, target: target} g := graph{min: minSteps, max: maxSteps, m: m, target: target, seenFn: seenFn}
cost, path := aoc.FindPath[int16, position](&g, position{loc: start}, position{loc: target}) cost, path := aoc.FindPath[int16, position](&g, position{loc: start}, position{loc: target})
log("total map reads = ", g.reads) log("total map reads = ", g.reads)

109
search.go
View File

@ -1,7 +1,6 @@
package aoc package aoc
import ( import (
"maps"
"sort" "sort"
) )
@ -74,9 +73,11 @@ func ManhattanDistance[T integer](a, b Point[T]) T {
type pather[C number, N comparable] interface { type pather[C number, N comparable] interface {
Neighbors(N) []N Neighbors(N) []N
Cost(a, b N) C Cost(a, b N) C
Potential(a, b N) C
// OPTIONAL: // OPTIONAL:
// Add heuristic for running as A* search.
// Potential(a, b N) C
// Seen modify value used by seen pruning. // Seen modify value used by seen pruning.
// Seen(N) N // Seen(N) N
@ -137,6 +138,11 @@ func FindPath[C integer, N comparable](g pather[C, N], start, end N) (C, []N) {
targetFn = s.Target targetFn = s.Target
} }
var potentialFn = func(a, b N) C { var zero C; return zero }
if s, ok := g.(interface{ Potential(a, b N) C }); ok {
potentialFn = s.Potential
}
for !pq.IsEmpty() { for !pq.IsEmpty() {
current, _ := pq.Dequeue() current, _ := pq.Dequeue()
cost, potential, n := current.cost, current.potential, current.position cost, potential, n := current.cost, current.potential, current.position
@ -162,7 +168,7 @@ func FindPath[C integer, N comparable](g pather[C, N], start, end N) (C, []N) {
position: nb, position: nb,
parent: &current, parent: &current,
cost: cost, cost: cost,
potential: g.Potential(nb, end), potential: potentialFn(nb, end),
} }
// check if path is in open list // check if path is in open list
if _, open := closed[seen]; !open { if _, open := closed[seen]; !open {
@ -173,100 +179,3 @@ 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(&current))
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: &current,
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
}