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] = ¤tPath } // 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 }