More work on this stupid task

This commit is contained in:
Melody Becker 2025-01-17 15:39:06 +01:00
parent 146306f57d
commit 608508dd89
2 changed files with 314 additions and 89 deletions

View file

@ -2,10 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"math"
"slices" "slices"
"git.mstar.dev/mstar/aoc24/util" "git.mstar.dev/mstar/aoc24/util"
"git.mstar.dev/mstar/goutils/sliceutils" "git.mstar.dev/mstar/goutils/sliceutils"
"github.com/RyanCarrier/dijkstra/v2"
) )
type Crossing struct { type Crossing struct {
@ -85,117 +87,333 @@ func sortPaths(area [][]rune) (all, straights, bends, crossings, deadEnds []util
return return
} }
// Only works when at a not-straight path node (crossing, bend, dead end) func findAdjacentTargets(
// TODO: Debug this area [][]rune,
func findAdjacentTargets(at util.Vec2, crossings, bends, deadEnds []util.Vec2) []util.Vec2 { at util.Vec2,
if sliceutils.Contains(crossings, at) || sliceutils.Contains(bends, at) || crossings, bends, deadEnds []util.Vec2,
sliceutils.Contains(deadEnds, at) { ) []util.Vec2 {
return []util.Vec2{} crossings = sliceutils.Filter(crossings, func(v util.Vec2) bool { return !v.Eq(at) })
} bends = sliceutils.Filter(bends, func(v util.Vec2) bool { return !v.Eq(at) })
deadEnds = sliceutils.Filter(deadEnds, func(v util.Vec2) bool { return !v.Eq(at) })
var posX, posY, negX, negY *util.Vec2
if util.SafeGet(area, at.Right()) != '#' {
crossingsPosX := sliceutils.Filter( crossingsPosX := sliceutils.Filter(
crossings, crossings,
func(t util.Vec2) bool { return at.X < t.X && at.Y == t.Y }, func(t util.Vec2) bool { return at.X < t.X && at.Y == t.Y },
) )
slices.SortFunc(crossingsPosX, func(a, b util.Vec2) int { return int(a.X - b.X) }) slices.SortFunc(crossingsPosX, func(a, b util.Vec2) int { return int(a.X - b.X) })
crossingsNegX := sliceutils.Filter( if len(crossingsPosX) > 0 {
crossings, posX = &crossingsPosX[0]
func(t util.Vec2) bool { return at.X > t.X && at.Y == t.Y }, }
)
slices.SortFunc(crossingsNegX, func(a, b util.Vec2) int { return int(b.X - a.X) })
crossingsPosY := sliceutils.Filter(
crossings,
func(t util.Vec2) bool { return at.Y < t.Y && at.X == t.X },
)
slices.SortFunc(crossingsPosY, func(a, b util.Vec2) int { return int(a.Y - b.Y) })
crossingsNegY := sliceutils.Filter(
crossings,
func(t util.Vec2) bool { return at.Y > t.Y && at.X == t.X },
)
slices.SortFunc(crossingsNegY, func(a, b util.Vec2) int { return int(b.Y - a.Y) })
bendsPosX := sliceutils.Filter( bendsPosX := sliceutils.Filter(
bends, bends,
func(t util.Vec2) bool { return at.X < t.X && at.Y == t.Y }, func(t util.Vec2) bool { return at.X < t.X && at.Y == t.Y },
) )
slices.SortFunc(bendsPosX, func(a, b util.Vec2) int { return int(a.X - b.X) }) slices.SortFunc(bendsPosX, func(a, b util.Vec2) int { return int(a.X - b.X) })
bendsNegX := sliceutils.Filter( if len(bendsPosX) > 0 {
bends, if posX == nil {
func(t util.Vec2) bool { return at.X > t.X && at.Y == t.Y }, posX = &bendsPosX[0]
) } else {
slices.SortFunc(bendsNegX, func(a, b util.Vec2) int { return int(b.X - a.X) }) if util.AbsI(posX.X-at.X) > util.AbsI(bendsPosX[0].X-at.X) {
bendsPosY := sliceutils.Filter( posX = &bendsPosX[0]
bends, }
func(t util.Vec2) bool { return at.Y < t.Y && at.X == t.X }, }
) }
slices.SortFunc(bendsPosY, func(a, b util.Vec2) int { return int(a.Y - b.Y) })
bendsNegY := sliceutils.Filter(
bends,
func(t util.Vec2) bool { return at.Y > t.Y && at.X == t.X },
)
slices.SortFunc(bendsNegY, func(a, b util.Vec2) int { return int(b.Y - a.Y) })
deadEndsPosX := sliceutils.Filter( deadEndsPosX := sliceutils.Filter(
deadEnds, deadEnds,
func(t util.Vec2) bool { return at.X < t.X && at.Y == t.Y }, func(t util.Vec2) bool { return at.X < t.X && at.Y == t.Y },
) )
slices.SortFunc(deadEndsPosX, func(a, b util.Vec2) int { return int(a.X - b.X) }) slices.SortFunc(deadEndsPosX, func(a, b util.Vec2) int { return int(a.X - b.X) })
if len(deadEndsPosX) > 0 {
if posX == nil {
posX = &deadEndsPosX[0]
} else {
if util.AbsI(posX.X-at.X) > util.AbsI(deadEndsPosX[0].X-at.X) {
posX = &deadEndsPosX[0]
}
}
}
}
if util.SafeGet(area, at.Left()) != '#' {
crossingsNegX := sliceutils.Filter(
crossings,
func(t util.Vec2) bool { return at.X > t.X && at.Y == t.Y },
)
slices.SortFunc(crossingsNegX, func(a, b util.Vec2) int { return int(b.X - a.X) })
if len(crossingsNegX) > 0 {
negX = &crossingsNegX[0]
}
bendsNegX := sliceutils.Filter(
bends,
func(t util.Vec2) bool { return at.X > t.X && at.Y == t.Y },
)
slices.SortFunc(bendsNegX, func(a, b util.Vec2) int { return int(b.X - a.X) })
if len(bendsNegX) > 0 {
if negX == nil {
negX = &bendsNegX[0]
} else {
if util.AbsI(negX.X-at.X) > util.AbsI(bendsNegX[0].X-at.X) {
negX = &bendsNegX[0]
}
}
}
deadEndsNegX := sliceutils.Filter( deadEndsNegX := sliceutils.Filter(
deadEnds, deadEnds,
func(t util.Vec2) bool { return at.X > t.X && at.Y == t.Y }, func(t util.Vec2) bool { return at.X > t.X && at.Y == t.Y },
) )
slices.SortFunc(deadEndsNegX, func(a, b util.Vec2) int { return int(b.X - a.X) }) slices.SortFunc(deadEndsNegX, func(a, b util.Vec2) int { return int(b.X - a.X) })
if len(deadEndsNegX) > 0 {
if negX == nil {
negX = &deadEndsNegX[0]
} else {
if util.AbsI(negX.X-at.X) > util.AbsI(deadEndsNegX[0].X-at.X) {
negX = &deadEndsNegX[0]
}
}
}
}
if util.SafeGet(area, at.Down()) != '#' {
crossingsPosY := sliceutils.Filter(
crossings,
func(t util.Vec2) bool { return at.Y < t.Y && at.X == t.X },
)
slices.SortFunc(crossingsPosY, func(a, b util.Vec2) int { return int(a.Y - b.Y) })
if len(crossingsPosY) > 0 {
posY = &crossingsPosY[0]
}
bendsPosY := sliceutils.Filter(
bends,
func(t util.Vec2) bool { return at.Y < t.Y && at.X == t.X },
)
slices.SortFunc(bendsPosY, func(a, b util.Vec2) int { return int(a.Y - b.Y) })
if len(bendsPosY) > 0 {
if posY == nil {
posY = &bendsPosY[0]
} else {
if util.AbsI(posY.Y-at.Y) < util.AbsI(bendsPosY[0].Y-at.Y) {
posY = &bendsPosY[0]
}
}
}
deadEndsPosY := sliceutils.Filter( deadEndsPosY := sliceutils.Filter(
deadEnds, deadEnds,
func(t util.Vec2) bool { return at.Y < t.Y && at.X == t.X }, func(t util.Vec2) bool { return at.Y < t.Y && at.X == t.X },
) )
slices.SortFunc(deadEndsPosY, func(a, b util.Vec2) int { return int(a.Y - b.Y) }) slices.SortFunc(deadEndsPosY, func(a, b util.Vec2) int { return int(a.Y - b.Y) })
if len(deadEndsPosY) > 0 {
if posY == nil {
posY = &deadEndsPosY[0]
} else {
if util.AbsI(posY.Y-at.Y) < util.AbsI(deadEndsPosY[0].Y-at.Y) {
posY = &deadEndsPosY[0]
}
}
}
}
if util.SafeGet(area, at.Up()) != '#' {
crossingsNegY := sliceutils.Filter(
crossings,
func(t util.Vec2) bool { return at.Y > t.Y && at.X == t.X },
)
slices.SortFunc(crossingsNegY, func(a, b util.Vec2) int { return int(b.Y - a.Y) })
if len(crossingsNegY) > 0 {
negY = &crossingsNegY[0]
}
bendsNegY := sliceutils.Filter(
bends,
func(t util.Vec2) bool { return at.Y > t.Y && at.X == t.X },
)
slices.SortFunc(bendsNegY, func(a, b util.Vec2) int { return int(b.Y - a.Y) })
if len(bendsNegY) > 0 {
if negY == nil {
negY = &bendsNegY[0]
} else {
if util.AbsI(negY.Y-at.Y) > util.AbsI(bendsNegY[0].Y-at.Y) {
negY = &bendsNegY[0]
}
}
}
deadEndsNegY := sliceutils.Filter( deadEndsNegY := sliceutils.Filter(
deadEnds, deadEnds,
func(t util.Vec2) bool { return at.Y > t.Y && at.X == t.X }, func(t util.Vec2) bool { return at.Y > t.Y && at.X == t.X },
) )
slices.SortFunc(deadEndsNegY, func(a, b util.Vec2) int { return int(b.Y - a.Y) }) slices.SortFunc(deadEndsNegY, func(a, b util.Vec2) int { return int(b.Y - a.Y) })
if len(deadEndsNegY) > 0 {
if negY == nil {
negY = &deadEndsNegY[0]
} else {
if util.AbsI(negY.Y-at.Y) > util.AbsI(deadEndsNegY[0].Y-at.Y) {
negY = &deadEndsNegY[0]
}
}
}
}
// fmt.Printf("posX %#v, negX %#v, posY %#v, negY %#v\n", posX, negX, posY, negY)
combined := []util.Vec2{} combined := []util.Vec2{}
combined = append(combined, crossingsPosX...) if posX != nil {
combined = append(combined, crossingsNegX...) combined = append(combined, *posX)
combined = append(combined, crossingsPosY...) }
combined = append(combined, crossingsNegY...) if negX != nil {
combined = append(combined, bendsPosX...) combined = append(combined, *negX)
combined = append(combined, bendsNegX...) }
combined = append(combined, bendsPosY...) if posY != nil {
combined = append(combined, bendsNegY...) combined = append(combined, *posY)
combined = append(combined, deadEndsPosX...) }
combined = append(combined, deadEndsNegX...) if negY != nil {
combined = append(combined, deadEndsPosY...) combined = append(combined, *negY)
combined = append(combined, deadEndsNegY...) }
return combined return combined
} }
func distanceBetween(start, end util.Vec2, crossings, bends, deadEnds []util.Vec2) (int, bool) { // Get the distance between two directly connecting points which must be either a crossing or a dead end
score := -1 func distanceBetween(
area [][]rune,
start, end util.Vec2,
crossings, bends, deadEnds []util.Vec2,
) (distance int, ok bool) {
// Locations are the same, note that and exit
if start.Eq(end) { if start.Eq(end) {
return 0, true return 0, true
} }
crossings = sliceutils.Filter(crossings, func(v util.Vec2) bool { return !v.Eq(start) }) // TODO: Refactor slightly to allow for distance to straights and bends too
return score, false
// Guard against start or end not being either a crossing or dead end
if !(sliceutils.Contains(crossings, start) || sliceutils.Contains(deadEnds, start)) &&
!(sliceutils.Contains(crossings, end) || sliceutils.Contains(deadEnds, end)) {
return 0, false
}
// Filter out start from crossing and dead ends to avoid recursion or zero length paths
// crossings = sliceutils.Filter(crossings, func(v util.Vec2) bool { return !v.Eq(start) })
// deadEnds = sliceutils.Filter(deadEnds, func(v util.Vec2) bool { return !v.Eq(start) })
adjacents := findAdjacentTargets(area, start, crossings, bends, deadEnds)
for _, adj := range adjacents {
distanceToStart := int(math.Round(start.Add(adj.Mult(-1)).Len()))
trackEnd, trackLen := followBend(area, adj, start, crossings, bends, deadEnds)
if trackEnd.Eq(end) {
return distanceToStart + trackLen, true
}
}
return -1, false
} }
func followBend( func followBend(
area [][]rune,
at, from util.Vec2, at, from util.Vec2,
crossings, bends, deadEnds []util.Vec2, crossings, bends, deadEnds []util.Vec2,
) (nextCrossingOrDeadEnd util.Vec2, distanceTravelled int) { ) (nextCrossingOrDeadEnd util.Vec2, distanceTravelled int) {
if sliceutils.Contains(crossings, at) { if sliceutils.Contains(crossings, at) {
return at, 0 return at, 0
} }
if sliceutils.Contains(deadEnds, at) {
return at, 0 return at, 0
}
adjacents := findAdjacentTargets(area, at, crossings, bends, deadEnds)
next := sliceutils.Filter(adjacents, func(v util.Vec2) bool { return !v.Eq(from) })[0]
diff := at.Add(next.Mult(-1))
diffLen := uint64(math.Round(diff.Len()))
end, lenToEnd := followBend(area, next, at, crossings, bends, deadEnds)
return end, int(diffLen) + 1000 + lenToEnd
}
func findAdjacentCrossingsAndDeadEnds(
area [][]rune,
at util.Vec2,
crossings, bends, deadEnds []util.Vec2,
) []util.Vec2 {
adjs := findAdjacentTargets(area, at, crossings, bends, deadEnds)
// fmt.Println("adjacents", adjs)
return sliceutils.Map(adjs, func(t util.Vec2) util.Vec2 {
end, _ := followBend(area, t, at, crossings, bends, deadEnds)
return end
})
}
func buildGraph(
area [][]rune,
crossings, bends, deadEnds []util.Vec2,
start, end util.Vec2,
) (graph *dijkstra.Graph, poi []util.Vec2, err error) {
g := dijkstra.NewGraph()
graph = &g
poi = slices.Clone(crossings)
poi = append(poi, deadEnds...)
poi = append(poi, start, end)
// Init all vertecies first
for i := range poi {
err = graph.AddEmptyVertex(i)
if err != nil {
return
}
}
for i, pos := range poi {
adjs := findAdjacentCrossingsAndDeadEnds(area, pos, crossings, bends, deadEnds)
for _, adj := range adjs {
index := slices.Index(poi, adj)
if index == -1 {
panic("Didn't find point in poi")
}
distance, _ := distanceBetween(area, pos, adj, crossings, bends, deadEnds)
err = graph.AddArc(i, index, uint64(distance))
if err != nil {
return
}
}
}
// TODO: Manually add end inbound connections
for _, locs := range findAdjacentCrossingsAndDeadEnds(area, end, crossings, bends, deadEnds) {
_ = locs
}
return
}
func findStartEnd(area [][]rune) (start util.Vec2, end util.Vec2) {
for iy, line := range area {
for ix, char := range line {
if char == 'S' {
start = util.Vec2{X: int64(ix), Y: int64(iy)}
} else if char == 'E' {
end = util.Vec2{X: int64(ix), Y: int64(iy)}
}
}
}
return
} }
func main() { func main() {
inputLines := util.FileContentToNonEmptyLines(util.LoadFileFromArgs()) inputLines := util.FileContentToNonEmptyLines(util.LoadFileFromArgs())
// size := getSize(inputLines) // size := getSize(inputLines)
area := sliceutils.Map(inputLines, func(t string) []rune { return []rune(t) }) area := sliceutils.Map(inputLines, func(t string) []rune { return []rune(t) })
start, end := findStartEnd(area)
_, _, bends, crossings, deadEnds := sortPaths(area) _, _, bends, crossings, deadEnds := sortPaths(area)
// fmt.Println("straights:", straights) // fmt.Println("straights:", straights)
fmt.Println("bends:", bends) // fmt.Println("bends:", bends)
fmt.Println("crossings:", crossings) // fmt.Println("crossings:", crossings)
fmt.Println("dead ends:", deadEnds) // fmt.Println("dead ends:", deadEnds)
fmt.Println("adjacent:", findAdjacentTargets(crossings[0], crossings, bends, deadEnds)) fmt.Println(
"start adjacents:",
findAdjacentCrossingsAndDeadEnds(area, start, crossings, bends, deadEnds),
)
fmt.Println(
"end adjacents:",
findAdjacentCrossingsAndDeadEnds(area, util.Vec2{X: 9, Y: 7}, crossings, bends, deadEnds),
)
dist, ok := distanceBetween(area, util.Vec2{X: 9, Y: 7}, end, crossings, bends, deadEnds)
fmt.Println("dist, ok:", dist, ok)
// fmt.Println("dist", distanceBetween(area [][]rune, start util.Vec2, end util.Vec2, crossings []util.Vec2, bends []util.Vec2, deadEnds []util.Vec2))
graph, pointsOfInterest, err := buildGraph(area, crossings, bends, deadEnds, start, end)
if err != nil {
panic(err)
}
shortest, err := graph.Shortest(
slices.Index(pointsOfInterest, start),
slices.Index(pointsOfInterest, end),
)
if err != nil {
panic(err)
}
fmt.Println("shortest", shortest)
} }

View file

@ -1,6 +1,9 @@
package util package util
import "fmt" import (
"fmt"
"math"
)
type Vec2 struct { type Vec2 struct {
X, Y int64 X, Y int64
@ -50,6 +53,10 @@ func (v Vec2) IsInBounds(bounds Vec2) bool {
return v.X >= 0 && v.X < bounds.X && v.Y >= 0 && v.Y < bounds.Y return v.X >= 0 && v.X < bounds.X && v.Y >= 0 && v.Y < bounds.Y
} }
func (v Vec2) Len() float64 {
return math.Sqrt(float64(v.LenSquared()))
}
func (v Vec2) String() string { func (v Vec2) String() string {
return fmt.Sprintf("(%d,%d)", v.X, v.Y) return fmt.Sprintf("(%d,%d)", v.X, v.Y)
} }