diff --git a/day17/clumsyCrucible.go b/day17/clumsyCrucible.go index 8303e9c..db6c49c 100644 --- a/day17/clumsyCrucible.go +++ b/day17/clumsyCrucible.go @@ -1,48 +1,28 @@ package day17 import ( - "container/heap" "fmt" "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) + log.Printf("%+v\n", field) - startSegment := PathSegmentEnd{ - endsAt: Coord{0, 0}, - totalLength: 0, - lastSteps: make(map[Direction]int), - done: true, - lastDirection: Downward, // fake, caution - } + field.RunDijkstra() end := Coord{field.Height - 1, field.Width - 1} - // field.getNextPathsToCheck(startSegment, end) - foundMin := field.runSearch(startSegment, end) - - // pathsFoundToEnd := field.Paths[end] - // fmt.Println("check visually:") + lenToEnd := field.Paths[end].totalLength + fmt.Println("check visually:") // fmt.Println(field.Paths[end].stringPathSoFar) - // 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 foundMin + fmt.Println(field.Paths[end].stringPathSoFar) + return lenToEnd } // let's do dijkstra. it also needs a priority queue @@ -101,6 +81,9 @@ func (c Coord) applyDirection(d Direction) (result Coord) { } return } +func (c Coord)String() string { + return fmt.Sprintf("(%d,%d)", c.Row, c.Col) +} type Direction int @@ -136,17 +119,12 @@ func (d Direction) GetPerpendicular() (directions []Direction) { } type PathSegmentEnd struct { - endsAt Coord + endsAt Coord totalLength int lastSteps map[Direction]int lastDirection Direction stringPathSoFar string done bool - score int // lover better -} - -func (p *PathSegmentEnd) IsExamplePathPrefix() bool { - return strings.HasPrefix(ExampleResult, p.stringPathSoFar) } func (p *PathSegmentEnd) NextDirections() (next []Direction) { @@ -158,40 +136,12 @@ func (p *PathSegmentEnd) NextDirections() (next []Direction) { next = append(next, p.lastDirection) } - // 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 @@ -200,15 +150,14 @@ type Field struct { func NewField(filename string) Field { enterCosts := ReadEnterCosts(filename) startSegment := PathSegmentEnd{ - endsAt: Coord{0, 0}, + endsAt: Coord{0, 0}, totalLength: 0, lastSteps: make(map[Direction]int), done: true, - lastDirection: Downward, // fake, caution - score: 0, + lastDirection: Downward, // fake, need to init direct neighbors also } - initialPaths := make(map[Coord][]*PathSegmentEnd) - initialPaths[Coord{0, 0}] = []*PathSegmentEnd{&startSegment} + initialPaths := make(map[Coord]*PathSegmentEnd) + initialPaths[Coord{0, 0}] = &startSegment return Field{ Paths: initialPaths, @@ -224,8 +173,10 @@ func (f *Field) isValid(c Coord) bool { } // presupposes that direction is valid -func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction, finish Coord) (result PathSegmentEnd) { - nextCoord := curPath.endsAt.applyDirection(d) +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) @@ -237,100 +188,96 @@ func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction, fin lastSteps[d] = curPathStepsIntoThisDirection + 1 } - newScore := newCost + (f.Height - nextCoord.Row) + (f.Width - nextCoord.Col) - return PathSegmentEnd{ - endsAt: nextCoord, - totalLength: newCost, - lastDirection: d, - lastSteps: lastSteps, + endsAt: nextCoord, + totalLength: newCost, + lastDirection: d, + lastSteps: lastSteps, stringPathSoFar: curPath.stringPathSoFar + d.AsSymbol(), - done: nextCoord == finish, - score: newScore, } } -func (f *Field) runSearch(startSegment PathSegmentEnd, finish Coord) int { - priorityQueue := make(PriorityQueue, 0) - - heap.Init(&priorityQueue) - heap.Push(&priorityQueue, &Item{value: startSegment}) - - min := math.MaxInt - for len(priorityQueue) > 0 { - currentCheck := heap.Pop(&priorityQueue).(*Item).value - - if currentCheck.endsAt == finish { - // log.Printf(">>>> found end %+v with len %d\n", currentCheck, currentCheck.totalLength) - if min > currentCheck.totalLength { - min = currentCheck.totalLength - log.Printf(">>>>>>>> found NEW MIN %+v with len %d\n", currentCheck, currentCheck.totalLength) - } - } - - nextPaths := f.getNextPathsToCheck(currentCheck, finish) - for _, next := range nextPaths { - heap.Push(&priorityQueue, &Item{value: next}) - } - } - - return min +func (p *PathSegmentEnd)StringKey() string { + return fmt.Sprintf("%s from %s with len %+v", p.endsAt.String(), p.lastDirection, p.lastSteps) } -func (f *Field) getNextPathsToCheck(curPath PathSegmentEnd, finish Coord) (furtherPathsToCheck []PathSegmentEnd) { +func (f *Field) RunDijkstra() { + checking := make([]PathSegmentEnd, 0) + distancesMap := make(map[string]int, 0) - // 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 { - // log.Printf(">> reached end with %+v\n", curPath) - if curPath.IsExamplePathPrefix() { - log.Printf(">> reached end with %+v\n", curPath) - } - return - } - directions := curPath.NextDirections() + startingPath := f.Paths[f.Start] + checking = append(checking, *startingPath) - if len(directions) == 0 { - return - } + distancesMap[startingPath.StringKey()] = 0 -checkingNeighbors: - for _, d := range directions { - nextCoord := curPath.endsAt.applyDirection(d) - if !f.isValid(nextCoord) { - continue - } - ponentialPath := f.continuePathInDirection(curPath, d, finish) - knownPathsToNeighbor := f.Paths[ponentialPath.endsAt] - for _, knownPath := range knownPathsToNeighbor { - if knownPath.isDominating(ponentialPath) { - continue checkingNeighbors + for len(checking) > 0 { + var currentPath PathSegmentEnd + selectingMinDistanceOfVisited := math.MaxInt + for _, path := range checking { + if path.totalLength < selectingMinDistanceOfVisited { + currentPath = path + selectingMinDistanceOfVisited = path.totalLength } - // 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 + currentCoord := currentPath.endsAt + directions := currentPath.NextDirections() - furtherPathsToCheck = append(furtherPathsToCheck, ponentialPath) - // f.runSearch(ponentialPath, finish) + for _, direction := range directions { + neighborCoord := currentCoord.applyDirection(direction) + if !f.isValid(neighborCoord) { + continue // prevent going off the grid + } + // log.Printf("from %+v will examine in direction %s to %+v", currentCoord, direction, neighborCoord) + // neighborPathSoFar, found := f.Paths[neighborCoord] + // if !found { + // neighborPathSoFar = &PathSegmentEnd{ + // totalLength: math.MaxInt, + // } + // f.Paths[neighborCoord] = neighborPathSoFar + // } + + pathIfWeGoFromCurrent := f.continuePathInDirection(currentPath, direction) + 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 } + + diff --git a/day17/notes.org b/day17/notes.org index a00ff90..cc4b974 100644 --- a/day17/notes.org +++ b/day17/notes.org @@ -4,3 +4,7 @@ and it's easy to imagine why. my guess is that i really should put 'paths to explore' into priority queue and select new ones not only by their length, but also by how far they go from the goal + +* lot's of time for no result +* so, for 'dijksra' don't store set of vertices, +but of ways we've entered them diff --git a/day17/priorityQueue.go b/day17/priorityQueue.go deleted file mode 100644 index 6895dca..0000000 --- a/day17/priorityQueue.go +++ /dev/null @@ -1,51 +0,0 @@ -package day17 - -import ( - "container/heap" -) - -// An Item is something we manage in a priority queue. -type Item struct { - value PathSegmentEnd // The value of the item; arbitrary. - // The index is needed by update and is maintained by the heap.Interface methods. - index int // The index of the item in the heap. -} - -// A PriorityQueue implements heap.Interface and holds Items. -type PriorityQueue []*Item - -func (pq PriorityQueue) Len() int { return len(pq) } - -func (pq PriorityQueue) Less(i, j int) bool { - // We want Pop to give us the lowest, so i modify snippet - return pq[i].value.score < pq[j].value.score -} - -func (pq PriorityQueue) Swap(i, j int) { - pq[i], pq[j] = pq[j], pq[i] - pq[i].index = i - pq[j].index = j -} - -func (pq *PriorityQueue) Push(x any) { - n := len(*pq) - item := x.(*Item) - item.index = n - *pq = append(*pq, item) -} - -func (pq *PriorityQueue) Pop() any { - old := *pq - n := len(old) - item := old[n-1] - old[n-1] = nil // avoid memory leak - item.index = -1 // for safety - *pq = old[0 : n-1] - return item -} - -// update modifies the priority and value of an Item in the queue. -func (pq *PriorityQueue) update(item *Item, value PathSegmentEnd) { - item.value = value - heap.Fix(pq, item.index) -}