Lets pick up work on aoc again. Maybe aim for completing it
This commit is contained in:
parent
d4cda3bfb5
commit
146306f57d
7 changed files with 220 additions and 444 deletions
201
day16/dijkstra.go
Normal file
201
day16/dijkstra.go
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/aoc24/util"
|
||||||
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Crossing struct {
|
||||||
|
At util.Vec2
|
||||||
|
Exits []util.Vec2
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connection struct {
|
||||||
|
From, To util.Vec2
|
||||||
|
Distance int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
DirUp = util.Vec2{X: 0, Y: -1}
|
||||||
|
DirRight = util.Vec2{X: 1, Y: 0}
|
||||||
|
DirDown = util.Vec2{X: 0, Y: 1}
|
||||||
|
DirLeft = util.Vec2{X: -1, Y: 0}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the size of the bord
|
||||||
|
func getSize(lines []string) util.Vec2 {
|
||||||
|
return util.Vec2{X: int64(len(lines[0])), Y: int64(len(lines))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the paths next to a given positon
|
||||||
|
func countPathsAt(lines [][]rune, at util.Vec2) []util.Vec2 {
|
||||||
|
center := lines[at.Y][at.X]
|
||||||
|
paths := []util.Vec2{}
|
||||||
|
if center == '#' {
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
if util.SafeGet(lines, at.Up()) != '#' {
|
||||||
|
paths = append(paths, at.Up())
|
||||||
|
}
|
||||||
|
if util.SafeGet(lines, at.Down()) != '#' {
|
||||||
|
paths = append(paths, at.Down())
|
||||||
|
}
|
||||||
|
if util.SafeGet(lines, at.Left()) != '#' {
|
||||||
|
paths = append(paths, at.Left())
|
||||||
|
}
|
||||||
|
if util.SafeGet(lines, at.Right()) != '#' {
|
||||||
|
paths = append(paths, at.Right())
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortPaths(area [][]rune) (all, straights, bends, crossings, deadEnds []util.Vec2) {
|
||||||
|
for iy, line := range area {
|
||||||
|
for ix, char := range line {
|
||||||
|
if char == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pos := util.Vec2{X: int64(ix), Y: int64(iy)}
|
||||||
|
all = append(all, pos)
|
||||||
|
entries := countPathsAt(area, pos)
|
||||||
|
switch len(entries) {
|
||||||
|
case 0:
|
||||||
|
continue
|
||||||
|
case 1:
|
||||||
|
deadEnds = append(deadEnds, pos)
|
||||||
|
case 2:
|
||||||
|
if entries[0].Right().Right().Eq(entries[1]) ||
|
||||||
|
entries[0].Left().Left().Eq(entries[1]) ||
|
||||||
|
entries[0].Up().Up().Eq(entries[1]) ||
|
||||||
|
entries[0].Down().Down().Eq(entries[1]) {
|
||||||
|
straights = append(straights, pos)
|
||||||
|
} else {
|
||||||
|
bends = append(bends, pos)
|
||||||
|
}
|
||||||
|
case 3, 4:
|
||||||
|
crossings = append(crossings, pos)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only works when at a not-straight path node (crossing, bend, dead end)
|
||||||
|
// TODO: Debug this
|
||||||
|
func findAdjacentTargets(at util.Vec2, crossings, bends, deadEnds []util.Vec2) []util.Vec2 {
|
||||||
|
if sliceutils.Contains(crossings, at) || sliceutils.Contains(bends, at) ||
|
||||||
|
sliceutils.Contains(deadEnds, at) {
|
||||||
|
return []util.Vec2{}
|
||||||
|
}
|
||||||
|
crossingsPosX := sliceutils.Filter(
|
||||||
|
crossings,
|
||||||
|
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) })
|
||||||
|
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) })
|
||||||
|
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(
|
||||||
|
bends,
|
||||||
|
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) })
|
||||||
|
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) })
|
||||||
|
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) })
|
||||||
|
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(
|
||||||
|
deadEnds,
|
||||||
|
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) })
|
||||||
|
deadEndsNegX := sliceutils.Filter(
|
||||||
|
deadEnds,
|
||||||
|
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) })
|
||||||
|
deadEndsPosY := sliceutils.Filter(
|
||||||
|
deadEnds,
|
||||||
|
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) })
|
||||||
|
deadEndsNegY := sliceutils.Filter(
|
||||||
|
deadEnds,
|
||||||
|
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) })
|
||||||
|
|
||||||
|
combined := []util.Vec2{}
|
||||||
|
combined = append(combined, crossingsPosX...)
|
||||||
|
combined = append(combined, crossingsNegX...)
|
||||||
|
combined = append(combined, crossingsPosY...)
|
||||||
|
combined = append(combined, crossingsNegY...)
|
||||||
|
combined = append(combined, bendsPosX...)
|
||||||
|
combined = append(combined, bendsNegX...)
|
||||||
|
combined = append(combined, bendsPosY...)
|
||||||
|
combined = append(combined, bendsNegY...)
|
||||||
|
combined = append(combined, deadEndsPosX...)
|
||||||
|
combined = append(combined, deadEndsNegX...)
|
||||||
|
combined = append(combined, deadEndsPosY...)
|
||||||
|
combined = append(combined, deadEndsNegY...)
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
func distanceBetween(start, end util.Vec2, crossings, bends, deadEnds []util.Vec2) (int, bool) {
|
||||||
|
score := -1
|
||||||
|
if start.Eq(end) {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
crossings = sliceutils.Filter(crossings, func(v util.Vec2) bool { return !v.Eq(start) })
|
||||||
|
return score, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func followBend(
|
||||||
|
at, from util.Vec2,
|
||||||
|
crossings, bends, deadEnds []util.Vec2,
|
||||||
|
) (nextCrossingOrDeadEnd util.Vec2, distanceTravelled int) {
|
||||||
|
if sliceutils.Contains(crossings, at) {
|
||||||
|
return at, 0
|
||||||
|
}
|
||||||
|
return at, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
inputLines := util.FileContentToNonEmptyLines(util.LoadFileFromArgs())
|
||||||
|
// size := getSize(inputLines)
|
||||||
|
area := sliceutils.Map(inputLines, func(t string) []rune { return []rune(t) })
|
||||||
|
_, _, bends, crossings, deadEnds := sortPaths(area)
|
||||||
|
// fmt.Println("straights:", straights)
|
||||||
|
fmt.Println("bends:", bends)
|
||||||
|
fmt.Println("crossings:", crossings)
|
||||||
|
fmt.Println("dead ends:", deadEnds)
|
||||||
|
fmt.Println("adjacent:", findAdjacentTargets(crossings[0], crossings, bends, deadEnds))
|
||||||
|
}
|
|
@ -1,220 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"git.mstar.dev/mstar/aoc24/util"
|
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
|
||||||
"github.com/RyanCarrier/dijkstra/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NodeType int
|
|
||||||
|
|
||||||
// A crossing is a path location with 3 or 4 other paths next to it
|
|
||||||
type Node struct {
|
|
||||||
At util.Vec2
|
|
||||||
Type NodeType
|
|
||||||
Dirs []util.Vec2
|
|
||||||
Index int
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
NodeInvalid NodeType = iota
|
|
||||||
NodeCrossing
|
|
||||||
NodeCorner
|
|
||||||
NodeDeadEnd
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DirUp = util.Vec2{X: 0, Y: -1}
|
|
||||||
DirRight = util.Vec2{X: 1, Y: 0}
|
|
||||||
DirDown = util.Vec2{X: 0, Y: 1}
|
|
||||||
DirLeft = util.Vec2{X: -1, Y: 0}
|
|
||||||
)
|
|
||||||
|
|
||||||
var startDir = DirRight
|
|
||||||
|
|
||||||
// Get the size of the bord
|
|
||||||
func getSize(lines []string) util.Vec2 {
|
|
||||||
return util.Vec2{X: int64(len(lines[0])), Y: int64(len(lines))}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the paths next to a given positon
|
|
||||||
func countPathsAt(lines [][]rune, at util.Vec2) []util.Vec2 {
|
|
||||||
center := lines[at.Y][at.X]
|
|
||||||
paths := []util.Vec2{}
|
|
||||||
if center == '#' {
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Up()) != '#' {
|
|
||||||
paths = append(paths, DirUp)
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Down()) != '#' {
|
|
||||||
paths = append(paths, DirDown)
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Left()) != '#' {
|
|
||||||
paths = append(paths, DirLeft)
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Right()) != '#' {
|
|
||||||
paths = append(paths, DirRight)
|
|
||||||
}
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCrossingsAndCornerPositions(lines [][]rune) []Node {
|
|
||||||
out := []Node{}
|
|
||||||
|
|
||||||
for iy, line := range lines {
|
|
||||||
for ix, char := range line {
|
|
||||||
if char == '#' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pos := util.Vec2{X: int64(ix), Y: int64(iy)}
|
|
||||||
dirs := countPathsAt(lines, pos)
|
|
||||||
// Crossings have 3 or more paths out
|
|
||||||
if len(dirs) >= 3 {
|
|
||||||
out = append(out, Node{pos, NodeCrossing, dirs, len(out)})
|
|
||||||
}
|
|
||||||
// Also include dead ends
|
|
||||||
if len(dirs) == 1 {
|
|
||||||
out = append(out, Node{pos, NodeDeadEnd, dirs, len(out)})
|
|
||||||
}
|
|
||||||
// Location is a corner if the paths are not opposite each other
|
|
||||||
if len(dirs) == 2 {
|
|
||||||
if !dirs[0].Mult(-1).Eq(dirs[1]) {
|
|
||||||
out = append(out, Node{pos, NodeCorner, dirs, len(out)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNeighbourNodes(nodes []Node, index int) []int {
|
|
||||||
target := nodes[index]
|
|
||||||
|
|
||||||
potential := sliceutils.Filter(nodes, func(t Node) bool {
|
|
||||||
return t.Index != index && (t.At.X == target.At.X || t.At.Y == target.At.Y)
|
|
||||||
})
|
|
||||||
|
|
||||||
hits := []Node{}
|
|
||||||
for _, dir := range target.Dirs {
|
|
||||||
switch dir {
|
|
||||||
case DirUp:
|
|
||||||
hits = append(hits, slices.MinFunc(potential, func(a, b Node) int {
|
|
||||||
if a.At.X != target.At.X || a.At.Y > target.At.Y {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if b.At.X != target.At.X || b.At.Y > target.At.Y {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return util.AbsI(
|
|
||||||
int(target.At.Y)-int(a.At.Y),
|
|
||||||
) - util.AbsI(
|
|
||||||
int(target.At.Y)-int(b.At.Y),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
case DirDown:
|
|
||||||
hits = append(hits, slices.MinFunc(potential, func(a, b Node) int {
|
|
||||||
if a.At.X != target.At.X || a.At.Y < target.At.Y {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if b.At.X != target.At.X || b.At.Y < target.At.Y {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return util.AbsI(
|
|
||||||
int(target.At.Y)-int(a.At.Y),
|
|
||||||
) - util.AbsI(
|
|
||||||
int(target.At.Y)-int(b.At.Y),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
case DirLeft:
|
|
||||||
hits = append(hits, slices.MinFunc(potential, func(a, b Node) int {
|
|
||||||
if a.At.Y != target.At.Y || a.At.X > target.At.X {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if b.At.Y != target.At.Y || b.At.X > target.At.X {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return util.AbsI(
|
|
||||||
int(target.At.X)-int(a.At.X),
|
|
||||||
) - util.AbsI(
|
|
||||||
int(target.At.X)-int(b.At.X),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
case DirRight:
|
|
||||||
hits = append(hits, slices.MinFunc(potential, func(a, b Node) int {
|
|
||||||
if a.At.Y != target.At.Y || a.At.X > target.At.X {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if b.At.Y != target.At.Y || b.At.X > target.At.X {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return util.AbsI(
|
|
||||||
int(target.At.X)-int(a.At.X),
|
|
||||||
) - util.AbsI(
|
|
||||||
int(target.At.X)-int(b.At.X),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
default:
|
|
||||||
panic("Unknown dir")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sliceutils.Map(hits, func(t Node) int { return t.Index })
|
|
||||||
}
|
|
||||||
|
|
||||||
func addNodesToGraph(nodes []Node, graph *dijkstra.Graph) {
|
|
||||||
for i := range len(nodes) {
|
|
||||||
if err := graph.AddEmptyVertex(i); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, node := range nodes {
|
|
||||||
for _, n := range getNeighbourNodes(nodes, node.Index) {
|
|
||||||
if err := graph.AddArc(node.Index, n, node.At.Add(nodes[n].At.Mult(-1)).LenSquared()); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findEnd(lines [][]rune) util.Vec2 {
|
|
||||||
for iy, line := range lines {
|
|
||||||
for ix, char := range line {
|
|
||||||
if char == 'E' {
|
|
||||||
return util.Vec2{X: int64(ix), Y: int64(iy)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return util.Vec2{X: -1, Y: -1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findStart(lines [][]rune) util.Vec2 {
|
|
||||||
for iy, line := range lines {
|
|
||||||
for ix, char := range line {
|
|
||||||
if char == 'S' {
|
|
||||||
return util.Vec2{X: int64(ix), Y: int64(iy)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return util.Vec2{X: -1, Y: -1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
inputLines := util.FileContentToNonEmptyLines(util.LoadFileFromArgs())
|
|
||||||
// size := getSize(inputLines)
|
|
||||||
inputLineChars := sliceutils.Map(inputLines, func(t string) []rune { return []rune(t) })
|
|
||||||
nodes := getCrossingsAndCornerPositions(inputLineChars)
|
|
||||||
// fmt.Println(nodes)
|
|
||||||
// slices.MaxFunc(x S, cmp func(a E, b E) int)
|
|
||||||
graph := dijkstra.NewGraph()
|
|
||||||
addNodesToGraph(nodes, &graph)
|
|
||||||
startVec := findStart(inputLineChars)
|
|
||||||
endVec := findEnd(inputLineChars)
|
|
||||||
startI := slices.IndexFunc(nodes, func(e Node) bool { return e.At == startVec })
|
|
||||||
endI := slices.IndexFunc(nodes, func(e Node) bool { return e.At == endVec })
|
|
||||||
|
|
||||||
path, err := graph.Shortest(startI, endI)
|
|
||||||
fmt.Println(path, err)
|
|
||||||
}
|
|
214
day16/main.go
214
day16/main.go
|
@ -1,214 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// Look at https://topaz.github.io/paste/#XQAAAQB1CAAAAAAAAAA4GEiZzRd1JAgz+whYRQxSFI7XvmlfhtGDinecZE7XVglOhhPLih7tF+1f6vGBXHx2f7N/fIfjMG8zy2b5K1bcEP2Fo6yufIOrK2Xv9CFqxAiFcqXF+B4dWDdaic6Mhmi96bCxTu3B1MuJ8XPiKzAsC1j5U5qhN1K9v/wrCnRnSs483gy4bYiKZacHfPN6AsZrOcqsgagmb5XZWYgYt6yR6lJ7yx0goiNLILnmpT8E9yBh5igU8VCxKT8lpj05Ce5ffUlcz4mCgQC5jenYOl86fCFPgFC05AvKYVOmGpjW8syIdhCLxhQJnhNsljM5wHIUezS9qwRsjXZTfItq30EnkIM9mdSPZY2/eGBlasPQi5LR+l5JFw9w3W3fesXYEB2u9yc/umujdgTI4RikSMdOfdYOrgCLuPFA72fHU3tGk2oyL5k58ZAR1SKFm+7XExv3gFPh6t+mrFjJ9QZ7qEb19gn0kMt2MaSiNLzyB8cJ9Dr3yxTGqfBOPsJPSI2jM2H1xgg1fu7gBZAYu5+KQ1CyctZ9mFzm25QxvrNyLFoNTPKn9ikZw7kUmqL6mIwu4bdJPwHivcPJTg0gNw03vByP90Yd/JGfGc+sFDd3V3R+LWLj3r3vIqs4QiSSgF/FOsQQjPPCt51Px1gtSR0opaqNeKHOAZirql6UaFuE/UPgUVH87T/q/Qi+jeLtc+pE3KySx42V4YJvPFEZKYCmGhrt1inGQXpS4K53V+VuCMBmo0jFayu8azXUyXaf2TgAtRNMmzCr2UmW+XnhCuMrdiEzj/vEH6WMrnJsfQlB8LrkwLYjdp16kMGavefwDk84W4C0lIcaDLRrxd+z5tN8+6jBpamqrsJy7BiKbDq7swhTXGoss0Xl2ZychI1h95+ZkFRQPm2ykeIwRcMwUIIA3WE8mXvo79noiPpQgasyUEqfGBEulP9/AaIidnEmK20OPFW4iwxCy/DinEok9tpMRocHYYQyyjFiqgd4D4blrPIDPjy0PE+zGhhrpyOOPEL7lEOoLCOHm8E54KuYX0eX1jnnjOSBlhO2//OOAmyWb2adhy5ir5ZWsqnXQaLo0FPwefYre1c6QuppDgiDgww0DhyroJ4YLvEn70ELBqlTIlcHI2XLDrD2wJF8lKa0TzrS3W+o3w86g1WSRLOWmcaCinUYEFSgaGsJzkNjkHS0IILRn2pWSxHqDxE5o2vR6ji41ijmNUDfq2gM9Gi+gTs+WgPKG+a4RX9qWY3aNqRleT89ZdORfsfz5/H1B0R7NDXcvoUY8fZ76k0ylqS1uc4Dc2ZTynjcufAHl4PbX9Sd/X1rXgvWac3RO5F8ztK8ESr1LqYZDqrQYNiwY+7F6P/W/BYU
|
|
||||||
// and https://topaz.github.io/paste/#XQAAAQDkCQAAAAAAAAA4GEiZzRd1JAgz+whYRQxSFI7XvmlfhtGDinecZE7XVglOhhPLih7tF+1f6vGBXHx2f7N/fIfjMG8zy2b5K1bcEP2Fo6yufIOrK2Xv9CFqxAiFcqXF+B4dWDZXMbthHyfiUnWQNzkV0hcT5wVw9evKcKh6zuVp40d6Delxu4q84FVJ2dhY/eNJLsgsqMaSnCN8+e4FFMobzvc4ZXLFZQrIjVJ0UJwQ7ZKOvJDlemAVoOBoABHsgOgAJCFm4JV2lELwBITIKT9LzmRsO7giPuslQ7Zsz3Hs9czf3v/ly4cWYb2QQMsQPahsGW2aIYGEflk2nhYJPAosdN9zZ285PGXNNrW5e9NHP5AnsS9lkvf3Kedlg2bQd+ByV/EhMGpwOQjNYG0JpZBF5rJzWVdsuYwvetCboF9ZqInecZftwTsJtyTV2P70EYjs5ipojuXXieZ6QG/XxI/aDOh4WXuWORVA0TxxMtLzuiUElWvTdAjA17uwb1/1i12tbP6s6EFNB6IMgaZnQv0o2zAcIH7em3/HdDXdRytvqC96G34ml/kFYUvvQu1tbK0f9lZIfUtPt30fKSM2zg4NJ60S2DNHt8zU6JvrGqyk/2CPSoeineqtMlQLoTm+a+jRr0FcUEpSMNWYm5bOpcllImxoVUXA7HV0FTDYM0yo8q/pMptPAp/971Z4HCMjsOyJTKuQeAXK8iIYqWaXXpcyhwaaDZ99+p8rlz5K1xAT308o5A4ZdcIALJ3ZEtNlD/9Y5aZ7+Ynngr4O72arMxGuS6wAUj001ocWXbSOEGlmGeCWVUdIEuRF2RQUlcBzFluDR9FeUkVoGqnjGLQ1MWSyRCfKm2btiWELjge8ge4TZEd7KVG5Nrm8cKrZDAYjZ0xkO8p8nnSTlIGJ1Vft1y7SEsxexvx0O0Flz4rQg1Z6SR1JNqOuTkGjhirnSdBN+r6Mvs2Fqb/Kq/FJlHe+kXSF1bitp2skfxdiFYqEEdL+unScYL7SAczwANBr/IiznSJyyL9zfBYvXQcVerkWkfAlm5ape09UaxgGv9LKREALhYAzN6AJDDB9c6tRbN+plzVIRN9CBmDVLReSIo7R8E0etA8L3yhbe2BBLfN4lL2gAXtzzJjYvkUp9raqCz61WMvHreUD2PhTazl+kwjM8IUzmo2DFrXU1ZzOF/T7XWXrjlqgOzBmEE/rldgY0ddx8e0vSj7gKD8EUIPAlR6trZcdCA0L2ENYbOisKQnTn/cfnHJl8NeXH0AOxNKw+V+mRh3W7S747+nBiRmseGkDM17g3MR6hoVdW3V3thtUuwgtP7VBfwdvjlumN1m2L3a6of+hqL7djDL3IzQQBnlhEdszcToONrRBmVRJOMf0ECPY5A9brSO1jR6sVTjQF1nKabW6tEFLuVHcr+BiKUP+pZKKz8amUuffmQixFGfeWUKS0RJTMJt8TVzczSpqNvaJ02r+5qgkN5PddKwMufcpMeTEQy4IRFTCwM6spWm9FVB/8uR3Htfv1KgA0Z6cSkH/+Y3ogw==
|
|
||||||
// Maybe also https://github.com/AhmedYassineMaalej/AoC-2024/blob/master/src/problems/day16.rs
|
|
||||||
// And more https://github.com/ayoubzulfiqar/advent-of-code/blob/main/2024/Go/Day16/part_1.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
|
||||||
|
|
||||||
"git.mstar.dev/mstar/aoc24/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A crossing is a path location with 3 or 4 other paths next to it
|
|
||||||
type Crossing struct {
|
|
||||||
At util.Vec2
|
|
||||||
LowestScore int
|
|
||||||
LowestFrom util.Vec2 // Index into list of crossings
|
|
||||||
Neighbours []util.Vec2 // Directions where a path is
|
|
||||||
}
|
|
||||||
|
|
||||||
type Area map[int64]map[int64]*Crossing
|
|
||||||
|
|
||||||
type Wrapper struct {
|
|
||||||
A Area
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
DirUp = util.Vec2{X: 0, Y: -1}
|
|
||||||
DirRight = util.Vec2{X: 1, Y: 0}
|
|
||||||
DirDown = util.Vec2{X: 0, Y: 1}
|
|
||||||
DirLeft = util.Vec2{X: -1, Y: 0}
|
|
||||||
)
|
|
||||||
|
|
||||||
var startDir = DirRight
|
|
||||||
|
|
||||||
// Get the size of the bord
|
|
||||||
func getSize(lines []string) util.Vec2 {
|
|
||||||
return util.Vec2{X: int64(len(lines[0])), Y: int64(len(lines))}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the paths next to a given positon
|
|
||||||
func countPathsAt(lines [][]rune, at util.Vec2) []util.Vec2 {
|
|
||||||
center := lines[at.Y][at.X]
|
|
||||||
paths := []util.Vec2{}
|
|
||||||
if center == '#' {
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Up()) != '#' {
|
|
||||||
paths = append(paths, at.Up())
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Down()) != '#' {
|
|
||||||
paths = append(paths, at.Down())
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Left()) != '#' {
|
|
||||||
paths = append(paths, at.Left())
|
|
||||||
}
|
|
||||||
if util.SafeGet(lines, at.Right()) != '#' {
|
|
||||||
paths = append(paths, at.Right())
|
|
||||||
}
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
func linesToMap(lines [][]rune, size util.Vec2) Area {
|
|
||||||
area := Area{}
|
|
||||||
// Ensure area has full maps
|
|
||||||
for i := range size.Y {
|
|
||||||
area[i] = map[int64]*Crossing{}
|
|
||||||
}
|
|
||||||
for iy, line := range lines {
|
|
||||||
for ix, char := range line {
|
|
||||||
pos := util.Vec2{X: int64(ix), Y: int64(iy)}
|
|
||||||
switch char {
|
|
||||||
case '.', 'E', 'S':
|
|
||||||
dirs := countPathsAt(lines, pos)
|
|
||||||
area[int64(ix)][int64(iy)] = &Crossing{
|
|
||||||
At: pos,
|
|
||||||
LowestScore: math.MaxInt64,
|
|
||||||
LowestFrom: util.Vec2{X: -1, Y: -1},
|
|
||||||
Neighbours: dirs,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return area
|
|
||||||
}
|
|
||||||
|
|
||||||
func findStart(lines [][]rune) util.Vec2 {
|
|
||||||
for iy, line := range lines {
|
|
||||||
for ix, char := range line {
|
|
||||||
if char == 'S' {
|
|
||||||
return util.Vec2{X: int64(ix), Y: int64(iy)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return invalid positon if none found
|
|
||||||
return util.Vec2{X: -1, Y: -1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findEnd(lines [][]rune) util.Vec2 {
|
|
||||||
for iy, line := range lines {
|
|
||||||
for ix, char := range line {
|
|
||||||
if char == 'E' {
|
|
||||||
return util.Vec2{X: int64(ix), Y: int64(iy)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return invalid positon if none found
|
|
||||||
return util.Vec2{X: -1, Y: -1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func walk(
|
|
||||||
area *Wrapper,
|
|
||||||
from util.Vec2,
|
|
||||||
dir util.Vec2,
|
|
||||||
score int,
|
|
||||||
) {
|
|
||||||
// Idea: walk forward from start until crossing or wall
|
|
||||||
// At crossing, defer a walker for both sides (if not walled off) but prioritise forwards
|
|
||||||
// At crossing, check if own score is lower than current score
|
|
||||||
// If it is, update score and set own origin
|
|
||||||
// Else if crossing has a lower score than self, abort
|
|
||||||
|
|
||||||
// Special case:
|
|
||||||
// If at end, update end "crossing" following the earlier rules
|
|
||||||
|
|
||||||
// Walk forward in dir until no node found
|
|
||||||
type WalkTarget struct {
|
|
||||||
Pos, Dir util.Vec2
|
|
||||||
Score int
|
|
||||||
}
|
|
||||||
// targets := []WalkTarget{}
|
|
||||||
defer fmt.Println("Deffered done", from)
|
|
||||||
fmt.Println("Starting", from)
|
|
||||||
prev := from
|
|
||||||
for pos := from.Add(dir); area.A[pos.X][pos.Y] != nil; pos = pos.Add(dir) {
|
|
||||||
score++
|
|
||||||
node := area.A[pos.X][pos.Y]
|
|
||||||
if node.LowestScore <= score {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node.LowestScore = score
|
|
||||||
node.LowestFrom = prev
|
|
||||||
// node.LowestFrom = pos.Add(dir.Mult(-1))
|
|
||||||
fmt.Println("Setting node", pos, score, node.LowestFrom)
|
|
||||||
// Get all directions that don't match current walking direction (and reverse)
|
|
||||||
filtered := sliceutils.Filter(node.Neighbours, func(t util.Vec2) bool {
|
|
||||||
return !t.Add(pos.Mult(-1)).Eq(dir) && !t.Add(pos.Mult(-1)).Eq(dir.Mult(-1))
|
|
||||||
})
|
|
||||||
// fmt.Println("Filtered neighbours of node", filtered)
|
|
||||||
for _, neighbour := range filtered {
|
|
||||||
fmt.Println("Adding target", neighbour)
|
|
||||||
// targets = append(targets, WalkTarget{
|
|
||||||
// neighbour, neighbour.Add(pos.Mult(-1)), score + 1001,
|
|
||||||
// })
|
|
||||||
defer walk(area, neighbour, neighbour.Add(pos.Mult(-1)), score+1001)
|
|
||||||
}
|
|
||||||
fmt.Println("Stepping", dir)
|
|
||||||
prev = pos
|
|
||||||
}
|
|
||||||
// fmt.Println("Hitting stored targets", from)
|
|
||||||
// for _, target := range targets {
|
|
||||||
// walk(area, target.Pos, target.Dir, target.Score)
|
|
||||||
// }
|
|
||||||
fmt.Println("Done, walking deferred", from)
|
|
||||||
}
|
|
||||||
|
|
||||||
func visualisePath(area Area, endPos, size util.Vec2, inputLineRunes [][]rune) string {
|
|
||||||
lineCopy := [][]rune{}
|
|
||||||
for _, line := range inputLineRunes {
|
|
||||||
lineCopy = append(lineCopy, slices.Clone(line))
|
|
||||||
}
|
|
||||||
for pos := endPos; pos.IsInBounds(size); pos = area[pos.X][pos.Y].LowestFrom {
|
|
||||||
fmt.Println("Path elem", pos)
|
|
||||||
lineCopy[pos.Y][pos.X] = 'x'
|
|
||||||
}
|
|
||||||
builder := strings.Builder{}
|
|
||||||
for _, line := range lineCopy {
|
|
||||||
for _, char := range line {
|
|
||||||
builder.WriteRune(char)
|
|
||||||
}
|
|
||||||
builder.WriteRune('\n')
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
inputLines := util.FileContentToNonEmptyLines(util.LoadFileFromArgs())
|
|
||||||
size := getSize(inputLines)
|
|
||||||
inputLineChars := sliceutils.Map(inputLines, func(t string) []rune { return []rune(t) })
|
|
||||||
area := linesToMap(inputLineChars, size)
|
|
||||||
|
|
||||||
start := findStart(inputLineChars)
|
|
||||||
end := findEnd(inputLineChars)
|
|
||||||
// Fill entire map
|
|
||||||
fmt.Println("Filling right")
|
|
||||||
wrapped := Wrapper{area}
|
|
||||||
walk(&wrapped, start, DirRight, 0)
|
|
||||||
fmt.Println("Filling up")
|
|
||||||
walk(&wrapped, start, DirUp, 1000)
|
|
||||||
fmt.Println("Filling down")
|
|
||||||
walk(&wrapped, start, DirDown, 1000)
|
|
||||||
fmt.Println("Filling left")
|
|
||||||
walk(&wrapped, start, DirLeft, 2000)
|
|
||||||
fmt.Printf("%#v\n", area[start.X][start.Y])
|
|
||||||
fmt.Printf("Task 1: %d\n", area[end.X][end.Y].LowestScore)
|
|
||||||
fmt.Println(visualisePath(area, end, size, inputLineChars))
|
|
||||||
}
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,5 @@ go 1.23.3
|
||||||
require git.mstar.dev/mstar/goutils v1.5.4
|
require git.mstar.dev/mstar/goutils v1.5.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/RyanCarrier/dijkstra v1.4.0 // indirect
|
|
||||||
github.com/RyanCarrier/dijkstra/v2 v2.0.2 // indirect
|
github.com/RyanCarrier/dijkstra/v2 v2.0.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,14 +19,6 @@ func LoadFileFromArgs() []byte {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func AbsI(a int) int {
|
|
||||||
if a > 0 {
|
|
||||||
return a
|
|
||||||
} else {
|
|
||||||
return -a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FileContentToNonEmptyLines(data []byte) []string {
|
func FileContentToNonEmptyLines(data []byte) []string {
|
||||||
return sliceutils.Filter(
|
return sliceutils.Filter(
|
||||||
strings.Split(string(data), "\n"),
|
strings.Split(string(data), "\n"),
|
||||||
|
|
14
util/math.go
14
util/math.go
|
@ -1,6 +1,10 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
func MaxI(x ...int) int {
|
type Unsigned interface {
|
||||||
|
int | int8 | int16 | int32 | int64 | float32 | float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaxI[T Unsigned](x ...T) T {
|
||||||
if len(x) == 0 {
|
if len(x) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -12,3 +16,11 @@ func MaxI(x ...int) int {
|
||||||
}
|
}
|
||||||
return tmp
|
return tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AbsI[T Unsigned](a T) T {
|
||||||
|
if a > 0 {
|
||||||
|
return a
|
||||||
|
} else {
|
||||||
|
return -a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type Vec2 struct {
|
type Vec2 struct {
|
||||||
X, Y int64
|
X, Y int64
|
||||||
}
|
}
|
||||||
|
@ -47,3 +49,7 @@ func (v Vec2) LenSquared() uint64 {
|
||||||
func (v Vec2) IsInBounds(bounds Vec2) bool {
|
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) String() string {
|
||||||
|
return fmt.Sprintf("(%d,%d)", v.X, v.Y)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue