diff --git a/day17/clumsyCrucible.go b/day17/clumsyCrucible.go index 557b4e3..0c14176 100644 --- a/day17/clumsyCrucible.go +++ b/day17/clumsyCrucible.go @@ -5,24 +5,42 @@ import ( "log" "math" "os" + "slices" "strings" // "time" ) +const ExampleResult string = ">>v>>>^>>>vv>>vv>vvv>vvv" + func Run() int { fmt.Println("hello from day 17") filename := "day17/example" field := NewField(filename) fmt.Printf("%+v\n", field) - field.RunDijkstra() + startSegment := PathSegmentEnd{ + endsAt: Coord{0, 0}, + totalLength: 0, + lastSteps: make(map[Direction]int), + done: true, + lastDirection: Downward, // fake, caution + } end := Coord{field.Height - 1, field.Width - 1} - lenToEnd := field.Paths[end].totalLength + field.runSearch(startSegment, end) + + pathsFoundToEnd := field.Paths[end] fmt.Println("check visually:") // fmt.Println(field.Paths[end].stringPathSoFar) - fmt.Println(field.printLastDirection()) - return lenToEnd + minimal := math.MaxInt + for _, path := range pathsFoundToEnd { + fmt.Printf("%+v ; len is %d\n", path, path.totalLength) + if path.totalLength < minimal { + minimal = path.totalLength + } + } + // fmt.Printf("i'm looking for %s\n", ExampleResult) + return minimal } // let's do dijkstra. it also needs a priority queue @@ -116,6 +134,7 @@ func (d Direction) GetPerpendicular() (directions []Direction) { } type PathSegmentEnd struct { + endsAt Coord totalLength int lastSteps map[Direction]int lastDirection Direction @@ -123,6 +142,10 @@ type PathSegmentEnd struct { done bool } +func (p *PathSegmentEnd) IsExamplePathPrefix() bool { + return strings.HasPrefix(ExampleResult, p.stringPathSoFar) +} + func (p *PathSegmentEnd) NextDirections() (next []Direction) { next = append(next, p.lastDirection.GetPerpendicular()...) @@ -132,12 +155,40 @@ func (p *PathSegmentEnd) NextDirections() (next []Direction) { next = append(next, p.lastDirection) } - log.Printf("getting directions from %+v they are %+v", p, next) + // if p.IsExamplePathPrefix() { + // log.Printf("getting directions from %+v they are %+v", p, next) + // } + // log.Printf("getting directions from %+v they are %+v", p, next) return } +func (p *PathSegmentEnd) isDominating(other PathSegmentEnd) bool { + if p.endsAt != other.endsAt { + panic(fmt.Sprintf("comparing domination of paths on different ells: %+v %+v", + p, other)) + } + var thisDirection Direction + var thisSteps int + for thisDirection, thisSteps = range p.lastSteps { + break + } + + var otherDirection Direction + var otherSteps int + for otherDirection, otherSteps = range other.lastSteps { + break + } + + sameDirection := thisDirection == otherDirection + // if other has less steps, then current total length is not important + // other can still be more efficient in the future + lessOrSameLastSteps := thisSteps <= otherSteps + + return sameDirection && lessOrSameLastSteps && p.totalLength < other.totalLength +} + type Field struct { - Paths map[Coord]*PathSegmentEnd + Paths map[Coord][]*PathSegmentEnd Costs [][]int Height, Width int Start Coord @@ -146,13 +197,14 @@ type Field struct { 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 + lastDirection: Downward, // fake, caution } - initialPaths := make(map[Coord]*PathSegmentEnd) - initialPaths[Coord{0, 0}] = &startSegment + initialPaths := make(map[Coord][]*PathSegmentEnd) + initialPaths[Coord{0, 0}] = []*PathSegmentEnd{&startSegment} return Field{ Paths: initialPaths, @@ -168,9 +220,8 @@ func (f *Field) isValid(c Coord) bool { } // presupposes that direction is valid -func (f *Field) continuePathInDirection(from Coord, d Direction) (result PathSegmentEnd) { - curPath := f.Paths[from] - nextCoord := from.applyDirection(d) +func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction, finish Coord) (result PathSegmentEnd) { + nextCoord := curPath.endsAt.applyDirection(d) moveCost := f.Costs[nextCoord.Row][nextCoord.Col] newCost := curPath.totalLength + moveCost lastSteps := make(map[Direction]int) @@ -183,73 +234,66 @@ func (f *Field) continuePathInDirection(from Coord, d Direction) (result PathSeg } return PathSegmentEnd{ - totalLength: newCost, - lastDirection: d, - lastSteps: lastSteps, + endsAt: nextCoord, + totalLength: newCost, + lastDirection: d, + lastSteps: lastSteps, stringPathSoFar: curPath.stringPathSoFar + d.AsSymbol(), + done: nextCoord == finish, } } -func (f *Field) RunDijkstra() { - checking := make(map[Coord]any, 0) +func (f *Field) runSearch(curPath PathSegmentEnd, finish Coord) { - checking[f.Start] = struct{}{} - - for len(checking) > 0 { - var currentCoord Coord - for key := range checking { - currentCoord = key - break + // if len(curPath.stringPathSoFar) > f.Height+f.Width { + // if curPath.IsExamplePathPrefix() { + // log.Printf(">> CUTOFF %+v\n", curPath) + // } + // return + // } + // if start is also finish : this path is done + // record the length, for the coord? + // get directions, + // if no directions : this path is done + // get neighbords, + // for each neighbor calc what whould be path if we went to the neighbor + // if neighbor already known path which DOMINATES current - do not continue + // + // in the end for each vertice there will be map[direction][]PathSegmentEnd + current := curPath.endsAt + if current == finish { + if curPath.IsExamplePathPrefix() { + log.Printf(">> reached end with %+v\n", curPath) } - currentPath := f.Paths[currentCoord] - directions := currentPath.NextDirections() + return + } + directions := curPath.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 - } + if len(directions) == 0 { + return + } - // 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{}{} +checkingNeighbors: + for _, d := range directions { + nextCoord := curPath.endsAt.applyDirection(d) + if !f.isValid(nextCoord) { + continue } - f.Paths[currentCoord].done = true - delete(checking, currentCoord) + ponentialPath := f.continuePathInDirection(curPath, d, finish) + knownPathsToNeighbor := f.Paths[ponentialPath.endsAt] + for _, knownPath := range knownPathsToNeighbor { + if knownPath.isDominating(ponentialPath) { + continue checkingNeighbors + } + // if our potential path is not dominated, then save as potential path + // and would be nice to remove all known paths which are dominated by this potential + } + filteredKnownPaths := slices.DeleteFunc(knownPathsToNeighbor, func(previous *PathSegmentEnd) bool { + return ponentialPath.isDominating(*previous) + }) + filteredKnownPaths = append(filteredKnownPaths, &ponentialPath) + f.Paths[ponentialPath.endsAt] = filteredKnownPaths - // fmt.Print(f.printLastDirection()) - // time.Sleep(time.Second) + f.runSearch(ponentialPath, finish) } } - -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 -} -