package main import ( "container/heap" log "github.com/sirupsen/logrus" ) type option func(*dijkstra) func WithMinDistance(distance int) option { return func(d *dijkstra) { d.minDistance = distance } } func WithMaxDistance(distance int) option { return func(d *dijkstra) { d.maxDistance = distance } } type dijkstra struct { graph [][]int minDistance int maxDistance int } func newDijkstra(graph [][]int, opts ...option) *dijkstra { d := &dijkstra{graph: graph} for _, opt := range opts { opt(d) } return d } func (d dijkstra) initialize(start coords) *pqueue { pq := newPriorityQueue() heap.Init(pq) // we don't encounter heat loss for start point unless we enter this block again heap.Push(pq, newNode(0, 0, 0, 0, start.X, start.Y)) return pq } // run performs the lowest cost dijkstra algorithm with a min heap func (d dijkstra) run(start, end coords) int { pq := d.initialize(start) visited := map[string]bool{} for pq.Len() > 0 { cost, node := func() (int, *node) { x := heap.Pop(pq).(*node) return x.cost, x }() // we reached final location, return its lowest cost if node.X == end.X && node.Y == end.Y && node.distance >= d.minDistance { log.Debug("returning lowest cost with min distance >= ", d.minDistance) return node.cost } if _, ok := visited[node.String()]; ok { continue } visited[node.String()] = true var neighbours = [][]int{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} // N, S, W, E for _, n := range neighbours { nextX := node.X + n[0] nextY := node.Y + n[1] if nextY < 0 || nextY >= len(d.graph) || nextX < 0 || nextX >= len(d.graph[nextY]) { continue } if node.directionX == -n[0] && node.directionY == -n[1] { // are we going backwards? continue } var distance = 1 if node.directionX == n[0] || node.directionY == n[1] { // same direction distance = node.distance + 1 } else { if node.distance < d.minDistance { continue } } if distance > d.maxDistance { continue } new_cost := cost + d.graph[nextY][nextX] heap.Push(pq, newNode(new_cost, distance, n[0], n[1], nextX, nextY)) } } return 0 } // one returns the lowest cost path from start to end func one(lines []string) int { graph := buildGraph(lines) start := newCoords(0, 0) end := newCoords(len(graph[0])-1, len(graph)-1) dijkstra := newDijkstra(graph, WithMaxDistance(3)) cost := dijkstra.run(start, end) return cost }