diff --git a/day17/main.go b/day17/main.go index f63b8da..f4b5b9f 100644 --- a/day17/main.go +++ b/day17/main.go @@ -29,10 +29,10 @@ func run(scan *bufio.Scanner) (*result, error) { log("start day 17") result := result{} - result.valuePT1 = search(m, 1, 3) + result.valuePT1 = search(m, 1, 3, seenFn) 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) return &result, nil @@ -90,6 +90,7 @@ type graph struct { m Map target Point reads int + seenFn func(a position) position } // 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 -func (g *graph) Potential(a, b position) int16 { - return aoc.ManhattanDistance(a.loc, b.loc) -} +// func (g *graph) Potential(a, b position) int16 { +// return aoc.ManhattanDistance(a.loc, b.loc) +// } func (g *graph) Target(a position) bool { 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. // It returns correct for part1 but not part 2.. // func (g *graph) Seen(a position) position { -// if a.direction == U { -// a.direction = D +// if g.seenFn != nil { +// return g.seenFn(a) // } -// if a.direction == L { -// a.direction = R -// } -// a.steps = 0 // 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() start := Point{} 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}) log("total map reads = ", g.reads) diff --git a/search.go b/search.go index fc305b2..7a7bc99 100644 --- a/search.go +++ b/search.go @@ -1,7 +1,6 @@ package aoc import ( - "maps" "sort" ) @@ -74,9 +73,11 @@ func ManhattanDistance[T integer](a, b Point[T]) T { type pather[C number, N comparable] interface { Neighbors(N) []N Cost(a, b N) C - Potential(a, b N) C // OPTIONAL: + // Add heuristic for running as A* search. + // Potential(a, b N) C + // Seen modify value used by seen pruning. // 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 } + 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() { current, _ := pq.Dequeue() 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, parent: ¤t, cost: cost, - potential: g.Potential(nb, end), + potential: potentialFn(nb, end), } // check if path is in open list 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 } - -// 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 -}