Advent-of-Code-2023/day17/clumsyCrucible.go

322 lines
8.5 KiB
Go

package day17
import (
"fmt"
"log"
"math"
"os"
"slices"
"strings"
)
func Run() int {
fmt.Println("hello from day 17")
filename := "day17/example"
field := NewField(filename)
log.Printf("%+v\n", field)
field.RunDijkstra()
lenToEnd := field.Paths[field.Finish].totalLength
fmt.Println("check visually:")
// fmt.Println(field.Paths[end].stringPathSoFar)
fmt.Println(field.Paths[field.Finish].stringPathSoFar)
return lenToEnd
}
// let's do dijkstra. it also needs a priority queue
// priority queue would be over vertice. and would have to have enough information to
// calc the distance from neighbors.
// how to check condition of max 3 in one row?
// with each vertice store [horizontal:n|vertical:n] and if it's 3 just dont consider?
// so in iteration, i have some vertice, with horizontal:2 for example,
// i check all neighbors, if path through 'this' is shorter, set that as path,
// but also mark the path with len of straight.
//
// so priority queue is with 'path to next'
// or rather 'path to i,j'
// then check for neighbors (non finished), calc distance to them through this
// checking neighbors via 'path get directions' 'path get geighbors from directions'
// if shorter - update
// mark current as 'finished'
// so, i'll be checking cost to enter directly from this table,
// but check path len
func ReadEnterCosts(filename string) [][]int {
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprint("error reading file ", filename))
}
text := strings.TrimSpace(string(bytes))
result := make([][]int, 0)
for _, line := range strings.Split(text, "\n") {
numbers := make([]int, 0)
for _, digit := range line {
num := int(digit - '0')
numbers = append(numbers, num)
}
result = append(result, numbers)
}
return result
}
type Coord struct {
Row, Col int
}
func (c Coord) applyDirection(d Direction) (result Coord) {
result = c
switch d {
case Upward:
result.Row -= 1
case Downward:
result.Row += 1
case Leftward:
result.Col -= 1
case Rightward:
result.Col += 1
}
return
}
func (c Coord)String() string {
return fmt.Sprintf("(%d,%d)", c.Row, c.Col)
}
type Direction int
const (
Upward Direction = iota
Downward
Leftward
Rightward
)
func (d Direction) String() string {
strings := []string{"Up", "Down", "Left", "Right"}
return strings[d]
}
func (d Direction) AsSymbol() string {
strings := []string{"^", "v", "<", ">"}
return strings[d]
}
func (d Direction) GetPerpendicular() (directions []Direction) {
switch d {
case Upward:
directions = []Direction{Leftward, Rightward}
case Downward:
directions = []Direction{Leftward, Rightward}
case Leftward:
directions = []Direction{Upward, Downward}
case Rightward:
directions = []Direction{Upward, Downward}
}
return
}
type PathSegmentEnd struct {
endsAt Coord
totalLength int
lastSteps map[Direction]int
lastDirection Direction
stringPathSoFar string
done bool
}
func (p *PathSegmentEnd) NextDirections2() (next []Direction) {
// last steps of 2 is max allowed 3 tiles in row
lastSteps := p.lastSteps[p.lastDirection]
if lastSteps < 4 {
return []Direction{p.lastDirection}
}
next = append(next, p.lastDirection.GetPerpendicular()...)
if lastSteps < 10 {
next = append(next, p.lastDirection)
}
log.Printf("getting directions from %+v they are %+v", p, next)
return
}
func (p *PathSegmentEnd) NextDirections() (next []Direction) {
next = append(next, p.lastDirection.GetPerpendicular()...)
// last steps of 2 is max allowed 3 tiles in row
lastSteps := p.lastSteps[p.lastDirection]
if lastSteps < 3 {
next = append(next, p.lastDirection)
}
// log.Printf("getting directions from %+v they are %+v", p, next)
return
}
type Field struct {
Paths map[Coord]*PathSegmentEnd
Costs [][]int
Height, Width int
Start Coord
Finish Coord
}
func NewField(filename string) Field {
enterCosts := ReadEnterCosts(filename)
startSegment := PathSegmentEnd{
endsAt: Coord{0, 0},
totalLength: 0,
lastSteps: make(map[Direction]int),
done: true,
lastDirection: Downward, // fake, need to init direct neighbors also
}
initialPaths := make(map[Coord]*PathSegmentEnd)
initialPaths[Coord{0, 0}] = &startSegment
height := len(enterCosts)
width := len(enterCosts[0])
return Field{
Paths: initialPaths,
Costs: enterCosts,
Height: height,
Width: width,
Start: Coord{0, 0},
Finish: Coord{height - 1, width - 1},
}
}
func (f *Field) isValid(c Coord) bool {
return c.Col >= 0 && c.Row >= 0 && c.Row < f.Height && c.Col < f.Width
}
// presupposes that direction is valid
func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction) (result PathSegmentEnd) {
// curPath := f.Paths[from]
from := curPath.endsAt
nextCoord := from.applyDirection(d)
moveCost := f.Costs[nextCoord.Row][nextCoord.Col]
newCost := curPath.totalLength + moveCost
lastSteps := make(map[Direction]int)
curPathStepsIntoThisDirection, found := curPath.lastSteps[d]
if !found {
lastSteps[d] = 1
} else {
lastSteps[d] = curPathStepsIntoThisDirection + 1
}
return PathSegmentEnd{
endsAt: nextCoord,
totalLength: newCost,
lastDirection: d,
lastSteps: lastSteps,
stringPathSoFar: curPath.stringPathSoFar + d.AsSymbol(),
}
}
func (p *PathSegmentEnd)StringKey() string {
return fmt.Sprintf("%s from %s with len %+v", p.endsAt.String(), p.lastDirection, p.lastSteps)
}
func (f *Field) RunDijkstra() {
checking := make([]PathSegmentEnd, 0)
distancesMap := make(map[string]int, 0)
startingPath := f.Paths[f.Start]
anotherStartingPath := PathSegmentEnd{
endsAt: Coord{0, 0},
totalLength: 0,
lastSteps: make(map[Direction]int),
done: true,
lastDirection: Rightward, // fake, need to init direct neighbors also
stringPathSoFar: ".",
}
checking = append(checking, *startingPath, anotherStartingPath)
distancesMap[startingPath.StringKey()] = 0
distancesMap[anotherStartingPath.StringKey()] = 0
for len(checking) > 0 {
var currentPath PathSegmentEnd
selectingMinDistanceOfVisited := math.MaxInt
for _, path := range checking {
if path.totalLength < selectingMinDistanceOfVisited {
currentPath = path
selectingMinDistanceOfVisited = path.totalLength
}
}
currentCoord := currentPath.endsAt
directions := currentPath.NextDirections2()
// fmt.Printf("> one more iteration for %+v ; directions will check %+v\n", currentPath, directions)
for _, direction := range directions {
neighborCoord := currentCoord.applyDirection(direction)
if !f.isValid(neighborCoord) {
continue // prevent going off the grid
}
// fmt.Printf("from %+v will examine in direction %s to %+v %+v\n", currentCoord, direction, neighborCoord, currentPath)
neighborPathSoFar, found := f.Paths[neighborCoord]
if !found {
neighborPathSoFar = &PathSegmentEnd{
totalLength: math.MaxInt,
}
f.Paths[neighborCoord] = neighborPathSoFar
}
pathIfWeGoFromCurrent := f.continuePathInDirection(currentPath, direction)
if pathIfWeGoFromCurrent.endsAt == f.Finish {
if pathIfWeGoFromCurrent.lastSteps[pathIfWeGoFromCurrent.lastDirection] < 4 {
continue
}
}
distFromThatSide, isKnown := distancesMap[pathIfWeGoFromCurrent.StringKey()]
if !isKnown {
distancesMap[pathIfWeGoFromCurrent.StringKey()] = pathIfWeGoFromCurrent.totalLength
// log.Printf("not known for %s \n", pathIfWeGoFromCurrent.StringKey())
checking = append(checking, pathIfWeGoFromCurrent)
}
if pathIfWeGoFromCurrent.totalLength < distFromThatSide {
f.Paths[neighborCoord] = &pathIfWeGoFromCurrent
// log.Printf("got update for %s \n", pathIfWeGoFromCurrent.StringKey())
distancesMap[pathIfWeGoFromCurrent.StringKey()] = pathIfWeGoFromCurrent.totalLength
checking = append(checking, pathIfWeGoFromCurrent)
} else {
continue // this path is better than existing
}
}
// f.Paths[currentCoord].done = true
checking = slices.DeleteFunc(checking, func (other PathSegmentEnd) bool { return other.stringPathSoFar == currentPath.stringPathSoFar })
storedPath, found := f.Paths[currentCoord]
if !found || storedPath.totalLength > currentPath.totalLength {
f.Paths[currentCoord] = &currentPath
}
// time.Sleep(time.Microsecond)
// fmt.Print(f.printLastDirection())
// time.Sleep(time.Second)
}
}
func (f *Field) printLastDirection() (result string) {
result += "\n"
for rowNum := 0; rowNum < f.Height; rowNum++ {
for colNum := 0; colNum < f.Width; colNum++ {
path, found := f.Paths[Coord{rowNum, colNum}]
if !found {
result += "."
} else {
result += path.lastDirection.AsSymbol()
}
}
result += "\n"
}
return
}