day17, so many tries for part 1

This commit is contained in:
efim 2023-12-17 13:28:18 +00:00
parent 81b8ddc8b0
commit abca885f20
3 changed files with 100 additions and 200 deletions

View File

@ -1,48 +1,28 @@
package day17 package day17
import ( import (
"container/heap"
"fmt" "fmt"
"log" "log"
"math" "math"
"os" "os"
"slices" "slices"
"strings" "strings"
// "time"
) )
const ExampleResult string = ">>v>>>^>>>vv>>vv>vvv>vvv<vv>"
func Run() int { func Run() int {
fmt.Println("hello from day 17") fmt.Println("hello from day 17")
filename := "day17/example" filename := "day17/example"
field := NewField(filename) field := NewField(filename)
fmt.Printf("%+v\n", field) log.Printf("%+v\n", field)
startSegment := PathSegmentEnd{ field.RunDijkstra()
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} end := Coord{field.Height - 1, field.Width - 1}
// field.getNextPathsToCheck(startSegment, end) lenToEnd := field.Paths[end].totalLength
foundMin := field.runSearch(startSegment, end) fmt.Println("check visually:")
// pathsFoundToEnd := field.Paths[end]
// fmt.Println("check visually:")
// fmt.Println(field.Paths[end].stringPathSoFar) // fmt.Println(field.Paths[end].stringPathSoFar)
// minimal := math.MaxInt fmt.Println(field.Paths[end].stringPathSoFar)
// for _, path := range pathsFoundToEnd { return lenToEnd
// 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
} }
// let's do dijkstra. it also needs a priority queue // let's do dijkstra. it also needs a priority queue
@ -101,6 +81,9 @@ func (c Coord) applyDirection(d Direction) (result Coord) {
} }
return return
} }
func (c Coord)String() string {
return fmt.Sprintf("(%d,%d)", c.Row, c.Col)
}
type Direction int type Direction int
@ -142,11 +125,6 @@ type PathSegmentEnd struct {
lastDirection Direction lastDirection Direction
stringPathSoFar string stringPathSoFar string
done bool done bool
score int // lover better
}
func (p *PathSegmentEnd) IsExamplePathPrefix() bool {
return strings.HasPrefix(ExampleResult, p.stringPathSoFar)
} }
func (p *PathSegmentEnd) NextDirections() (next []Direction) { func (p *PathSegmentEnd) NextDirections() (next []Direction) {
@ -158,40 +136,12 @@ func (p *PathSegmentEnd) NextDirections() (next []Direction) {
next = append(next, p.lastDirection) 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) // log.Printf("getting directions from %+v they are %+v", p, next)
return 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 { type Field struct {
Paths map[Coord][]*PathSegmentEnd Paths map[Coord]*PathSegmentEnd
Costs [][]int Costs [][]int
Height, Width int Height, Width int
Start Coord Start Coord
@ -204,11 +154,10 @@ func NewField(filename string) Field {
totalLength: 0, totalLength: 0,
lastSteps: make(map[Direction]int), lastSteps: make(map[Direction]int),
done: true, done: true,
lastDirection: Downward, // fake, caution lastDirection: Downward, // fake, need to init direct neighbors also
score: 0,
} }
initialPaths := make(map[Coord][]*PathSegmentEnd) initialPaths := make(map[Coord]*PathSegmentEnd)
initialPaths[Coord{0, 0}] = []*PathSegmentEnd{&startSegment} initialPaths[Coord{0, 0}] = &startSegment
return Field{ return Field{
Paths: initialPaths, Paths: initialPaths,
@ -224,8 +173,10 @@ func (f *Field) isValid(c Coord) bool {
} }
// presupposes that direction is valid // presupposes that direction is valid
func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction, finish Coord) (result PathSegmentEnd) { func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction) (result PathSegmentEnd) {
nextCoord := curPath.endsAt.applyDirection(d) // curPath := f.Paths[from]
from := curPath.endsAt
nextCoord := from.applyDirection(d)
moveCost := f.Costs[nextCoord.Row][nextCoord.Col] moveCost := f.Costs[nextCoord.Row][nextCoord.Col]
newCost := curPath.totalLength + moveCost newCost := curPath.totalLength + moveCost
lastSteps := make(map[Direction]int) lastSteps := make(map[Direction]int)
@ -237,100 +188,96 @@ func (f *Field) continuePathInDirection(curPath PathSegmentEnd, d Direction, fin
lastSteps[d] = curPathStepsIntoThisDirection + 1 lastSteps[d] = curPathStepsIntoThisDirection + 1
} }
newScore := newCost + (f.Height - nextCoord.Row) + (f.Width - nextCoord.Col)
return PathSegmentEnd{ return PathSegmentEnd{
endsAt: nextCoord, endsAt: nextCoord,
totalLength: newCost, totalLength: newCost,
lastDirection: d, lastDirection: d,
lastSteps: lastSteps, lastSteps: lastSteps,
stringPathSoFar: curPath.stringPathSoFar + d.AsSymbol(), stringPathSoFar: curPath.stringPathSoFar + d.AsSymbol(),
done: nextCoord == finish,
score: newScore,
} }
} }
func (f *Field) runSearch(startSegment PathSegmentEnd, finish Coord) int { func (p *PathSegmentEnd)StringKey() string {
priorityQueue := make(PriorityQueue, 0) return fmt.Sprintf("%s from %s with len %+v", p.endsAt.String(), p.lastDirection, p.lastSteps)
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) func (f *Field) RunDijkstra() {
for _, next := range nextPaths { checking := make([]PathSegmentEnd, 0)
heap.Push(&priorityQueue, &Item{value: next}) distancesMap := make(map[string]int, 0)
startingPath := f.Paths[f.Start]
checking = append(checking, *startingPath)
distancesMap[startingPath.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.NextDirections()
return min 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)
func (f *Field) getNextPathsToCheck(curPath PathSegmentEnd, finish Coord) (furtherPathsToCheck []PathSegmentEnd) { // neighborPathSoFar, found := f.Paths[neighborCoord]
// if !found {
// if len(curPath.stringPathSoFar) > f.Height+f.Width { // neighborPathSoFar = &PathSegmentEnd{
// if curPath.IsExamplePathPrefix() { // totalLength: math.MaxInt,
// log.Printf(">> CUTOFF %+v\n", curPath)
// } // }
// return // f.Paths[neighborCoord] = neighborPathSoFar
// } // }
// if start is also finish : this path is done
// record the length, for the coord? pathIfWeGoFromCurrent := f.continuePathInDirection(currentPath, direction)
// get directions, distFromThatSide, isKnown := distancesMap[pathIfWeGoFromCurrent.StringKey()]
// if no directions : this path is done if !isKnown {
// get neighbords, distancesMap[pathIfWeGoFromCurrent.StringKey()] = pathIfWeGoFromCurrent.totalLength
// for each neighbor calc what whould be path if we went to the neighbor // log.Printf("not known for %s \n", pathIfWeGoFromCurrent.StringKey())
// if neighbor already known path which DOMINATES current - do not continue checking = append(checking, pathIfWeGoFromCurrent)
// }
// in the end for each vertice there will be map[direction][]PathSegmentEnd if pathIfWeGoFromCurrent.totalLength < distFromThatSide {
current := curPath.endsAt f.Paths[neighborCoord] = &pathIfWeGoFromCurrent
if current == finish { // log.Printf("got update for %s \n", pathIfWeGoFromCurrent.StringKey())
// log.Printf(">> reached end with %+v\n", curPath) distancesMap[pathIfWeGoFromCurrent.StringKey()] = pathIfWeGoFromCurrent.totalLength
if curPath.IsExamplePathPrefix() { checking = append(checking, pathIfWeGoFromCurrent)
log.Printf(">> reached end with %+v\n", curPath) } 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 return
} }
directions := curPath.NextDirections()
if len(directions) == 0 {
return
}
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
}
// 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
furtherPathsToCheck = append(furtherPathsToCheck, ponentialPath)
// f.runSearch(ponentialPath, finish)
}
return
}

View File

@ -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 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 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

View File

@ -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)
}