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
@ -136,17 +119,12 @@ func (d Direction) GetPerpendicular() (directions []Direction) {
} }
type PathSegmentEnd struct { type PathSegmentEnd struct {
endsAt Coord endsAt Coord
totalLength int totalLength int
lastSteps map[Direction]int lastSteps map[Direction]int
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
@ -200,15 +150,14 @@ type Field struct {
func NewField(filename string) Field { func NewField(filename string) Field {
enterCosts := ReadEnterCosts(filename) enterCosts := ReadEnterCosts(filename)
startSegment := PathSegmentEnd{ startSegment := PathSegmentEnd{
endsAt: Coord{0, 0}, endsAt: Coord{0, 0},
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)
for _, next := range nextPaths {
heap.Push(&priorityQueue, &Item{value: next})
}
}
return min
} }
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 { startingPath := f.Paths[f.Start]
// if curPath.IsExamplePathPrefix() { checking = append(checking, *startingPath)
// 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()
if len(directions) == 0 { distancesMap[startingPath.StringKey()] = 0
return
}
checkingNeighbors: for len(checking) > 0 {
for _, d := range directions { var currentPath PathSegmentEnd
nextCoord := curPath.endsAt.applyDirection(d) selectingMinDistanceOfVisited := math.MaxInt
if !f.isValid(nextCoord) { for _, path := range checking {
continue if path.totalLength < selectingMinDistanceOfVisited {
} currentPath = path
ponentialPath := f.continuePathInDirection(curPath, d, finish) selectingMinDistanceOfVisited = path.totalLength
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 { currentCoord := currentPath.endsAt
return ponentialPath.isDominating(*previous) directions := currentPath.NextDirections()
})
filteredKnownPaths = append(filteredKnownPaths, &ponentialPath)
f.Paths[ponentialPath.endsAt] = filteredKnownPaths
furtherPathsToCheck = append(furtherPathsToCheck, ponentialPath) for _, direction := range directions {
// f.runSearch(ponentialPath, finish) 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] = &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
} }

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