day17, so many tries for part 1
This commit is contained in:
parent
81b8ddc8b0
commit
abca885f20
|
@ -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] = ¤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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
Loading…
Reference in New Issue