diff --git a/day17/clumsyCrucible.go b/day17/clumsyCrucible.go new file mode 100644 index 0000000..557b4e3 --- /dev/null +++ b/day17/clumsyCrucible.go @@ -0,0 +1,255 @@ +package day17 + +import ( + "fmt" + "log" + "math" + "os" + "strings" + // "time" +) + +func Run() int { + fmt.Println("hello from day 17") + filename := "day17/example" + field := NewField(filename) + fmt.Printf("%+v\n", field) + + field.RunDijkstra() + + end := Coord{field.Height - 1, field.Width - 1} + lenToEnd := field.Paths[end].totalLength + fmt.Println("check visually:") + // fmt.Println(field.Paths[end].stringPathSoFar) + fmt.Println(field.printLastDirection()) + 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 +} + +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 { + totalLength int + lastSteps map[Direction]int + lastDirection Direction + stringPathSoFar string + done bool +} + +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 +} + +func NewField(filename string) Field { + enterCosts := ReadEnterCosts(filename) + startSegment := PathSegmentEnd{ + 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 + + return Field{ + Paths: initialPaths, + Costs: enterCosts, + Height: len(enterCosts), + Width: len(enterCosts[0]), + Start: Coord{0, 0}, + } +} + +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(from Coord, d Direction) (result PathSegmentEnd) { + curPath := f.Paths[from] + 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{ + totalLength: newCost, + lastDirection: d, + lastSteps: lastSteps, + stringPathSoFar: curPath.stringPathSoFar + d.AsSymbol(), + } +} + +func (f *Field) RunDijkstra() { + checking := make(map[Coord]any, 0) + + checking[f.Start] = struct{}{} + + for len(checking) > 0 { + var currentCoord Coord + for key := range checking { + currentCoord = key + break + } + currentPath := f.Paths[currentCoord] + directions := currentPath.NextDirections() + + for _, direction := range directions { + log.Printf("from %+v will examine in direction %s", currentCoord, direction) + neighborCoord := currentCoord.applyDirection(direction) + if !f.isValid(neighborCoord) { + continue // prevent going off the grid + } + neighborPathSoFar, found := f.Paths[neighborCoord] + if !found { + neighborPathSoFar = &PathSegmentEnd{ + totalLength: math.MaxInt, + } + f.Paths[neighborCoord] = neighborPathSoFar + } + + // log.Printf("about to check done on neighbor %+v\n", neighborPathSoFar) + if neighborPathSoFar.done { + continue // already found optimal + } + + pathIfWeGoFromCurrent := f.continuePathInDirection(currentCoord, direction) + if pathIfWeGoFromCurrent.totalLength < neighborPathSoFar.totalLength { + f.Paths[neighborCoord] = &pathIfWeGoFromCurrent + } + checking[neighborCoord] = struct{}{} + } + f.Paths[currentCoord].done = true + delete(checking, currentCoord) + + // 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 +} + diff --git a/day17/example b/day17/example new file mode 100644 index 0000000..f400d6e --- /dev/null +++ b/day17/example @@ -0,0 +1,13 @@ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533 diff --git a/main.go b/main.go index 28e6220..9d021e7 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,12 @@ package main import ( "log" - "sunshine.industries/aoc2023/day16" + "sunshine.industries/aoc2023/day17" ) func main() { log.Print("> starting run:") - result := day16.Run() - log.Printf("\n\nday16 result: %d\n****\n", result) + result := day17.Run() + log.Printf("\n\nday17 result: %d\n****\n", result) }