Day - 12
Part 1 - Code
package main
import (
"bufio"
"errors"
"fmt"
"log"
"math"
"os"
)
var year = 2022
var day = 12
var path = os.Getenv("ADVENT_HOME")
func findInGrid(grid [][]rune, find rune) (int, int) {
i, j := 0, 0
for r, row := range grid {
for c, col := range row {
if col == find {
i, j = r, c
break
}
}
}
return i, j
}
func contains(s []Pair, e Pair) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func inGrid(grid [][]rune, at Pair) bool {
x, y := at.x, at.y
return 0 <= x && x < len(grid) && 0 <= y && y < len(grid[x])
}
func canClimb(grid [][]rune, at Pair, to Pair) bool {
if inGrid(grid, at) && inGrid(grid, to) {
atRune := grid[at.x][at.y]
toRune := grid[to.x][to.y]
cond := toRune-atRune <= 1
// fmt.Printf("atRune=%s toRune=%s cond=%t\n", string(atRune), string(toRune), cond)
return cond
}
return false
}
func dfs(grid [][]rune, at Pair, end Pair, trail []Pair, count *int) {
if inGrid(grid, at) && !contains(trail, at) {
if at == end {
fmt.Printf("found path %d count=%d\n", len(trail), *count)
if len(trail) < *count {
fmt.Printf("found shorter path %d\n", len(trail))
*count = len(trail)
}
return
} else {
trail := append(trail, at)
next := Pair{at.x + 1, at.y}
if inGrid(grid, next) && canClimb(grid, at, next) {
dfs(grid, next, end, trail, count)
}
next = Pair{at.x, at.y + 1}
if inGrid(grid, next) && canClimb(grid, at, next) {
dfs(grid, next, end, trail, count)
}
next = Pair{at.x - 1, at.y}
if inGrid(grid, next) && canClimb(grid, at, next) {
dfs(grid, next, end, trail, count)
}
next = Pair{at.x, at.y - 1}
if inGrid(grid, next) && canClimb(grid, at, next) {
dfs(grid, next, end, trail, count)
}
}
}
}
func AbsInt(x int) int {
if x < 0 {
return -x
}
return x
}
func h(grid [][]rune, at Pair, end Pair) int {
return AbsInt(at.x-end.x) + AbsInt(at.y-end.y)
}
func getLowestFScore(openSet map[Pair]bool, fScore map[Pair]int) Pair {
lowestKey := Pair{}
for k, _ := range openSet {
lowestKey = k
break
}
for k, _ := range openSet {
if getScore(fScore, k) < getScore(fScore, lowestKey) {
lowestKey = k
}
}
return lowestKey
}
func getNeighbors(grid [][]rune, at Pair) []Pair {
neighs := []Pair{}
next := Pair{at.x + 1, at.y}
if inGrid(grid, next) && canClimb(grid, at, next) {
neighs = append(neighs, next)
}
next = Pair{at.x, at.y + 1}
if inGrid(grid, next) && canClimb(grid, at, next) {
neighs = append(neighs, next)
}
next = Pair{at.x - 1, at.y}
if inGrid(grid, next) && canClimb(grid, at, next) {
neighs = append(neighs, next)
}
next = Pair{at.x, at.y - 1}
if inGrid(grid, next) && canClimb(grid, at, next) {
neighs = append(neighs, next)
}
return neighs
}
func containsKey(cameFrom map[Pair]Pair, at Pair) bool {
_, ok := cameFrom[at]
return ok
}
func reconstructPath(cameFrom map[Pair]Pair, at Pair) []Pair {
path := []Pair{}
path = append(path, at)
for containsKey(cameFrom, at) {
at = cameFrom[at]
path = append(path, at)
}
return path
}
func getScore(score map[Pair]int, at Pair) int {
if val, ok := score[at]; ok {
return val
} else {
return math.MaxInt
}
}
func astar(grid [][]rune, start Pair, end Pair, trail []Pair) ([]Pair, error) {
openSet := make(map[Pair]bool)
cameFrom := make(map[Pair]Pair)
gScore := make(map[Pair]int)
gScore[start] = 0
fScore := make(map[Pair]int)
fScore[start] = h(grid, start, end)
openSet[start] = true
for len(openSet) > 0 {
current := getLowestFScore(openSet, fScore)
// fmt.Println(current)
if current == end {
return reconstructPath(cameFrom, current), nil
}
delete(openSet, current)
neighbors := getNeighbors(grid, current)
// fmt.Println(neighbors)
for _, neighbor := range neighbors {
// fmt.Println(neighbor, gScore[neighbor])
tentativeGScore := gScore[current] + 1
if tentativeGScore < getScore(gScore, neighbor) {
cameFrom[neighbor] = current
gScore[neighbor] = tentativeGScore
fScore[neighbor] = tentativeGScore + h(grid, neighbor, end)
if _, ok := openSet[neighbor]; !ok {
openSet[neighbor] = true
}
}
}
}
return []Pair{}, errors.New("no path")
}
type Pair struct {
x, y int
}
func (p Pair) String() string {
return fmt.Sprintf("(%d,%d)", p.x, p.y)
}
func main() {
dataPath := fmt.Sprintf("%s/%d/data/day%d/data.txt", path, year, day)
fmt.Println(dataPath)
file, err := os.Open(dataPath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
grid := [][]rune{}
for scanner.Scan() {
line := scanner.Text()
row := []rune(line)
grid = append(grid, row)
}
sx, sy := findInGrid(grid, 'S')
ex, ey := findInGrid(grid, 'E')
start := Pair{sx, sy}
end := Pair{ex, ey}
grid[start.x][start.y] = 'a'
grid[end.x][end.y] = 'z'
// dfs(grid, start, end, []Pair{}, &count)
// fmt.Printf("count is %d\n", count)
path, err := astar(grid, start, end, []Pair{})
if err == nil {
fmt.Printf("count is %d\n", len(path)-1)
} else {
log.Fatal(err)
}
aStartPaths := []Pair{}
for r, row := range grid {
for c, col := range row {
if col == 'a' {
aStartPaths = append(aStartPaths, Pair{r, c})
}
}
}
// NOTE: this (and therefore also the previous one given we can use one computation) can be done more efficiently with dykstra
minAPath := math.MaxInt
for _, pair := range aStartPaths {
path, err := astar(grid, pair, end, []Pair{})
if err == nil {
if (len(path) - 1) < minAPath {
minAPath = (len(path) - 1)
}
}
}
fmt.Printf("min path length is %d\n", minAPath)
}