From 5ae4d0ca9be12eb7e06b8678d653bca68731667c Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Tue, 17 Dec 2024 12:47:16 -0600 Subject: [PATCH] 2024 Day 16 Complete! --- 2024/day16/main.go | 334 ++++++++++++------------------------------ 2024/day16/main.go.bk | 276 ++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+), 243 deletions(-) create mode 100644 2024/day16/main.go.bk diff --git a/2024/day16/main.go b/2024/day16/main.go index e990d69..0e3987f 100644 --- a/2024/day16/main.go +++ b/2024/day16/main.go @@ -1,276 +1,124 @@ package main import ( - "errors" "fmt" + "math" h "git.bullercodeworks.com/brian/adventofcode/helpers" ) func main() { - inp := h.StdinToStringSlice() - part1(inp) - // fmt.Println() - // part2(inp) -} - -var ( - N = h.Coordinate{X: 0, Y: -1} - E = h.Coordinate{X: 1, Y: 0} - S = h.Coordinate{X: 0, Y: 1} - W = h.Coordinate{X: -1, Y: 0} -) - -var ( - scoreTrack map[h.Coordinate]int - bestCostToEnd map[h.Coordinate]int -) - -func part1(inpS []string) { - inp := h.StringSliceToCoordByteMap(inpS) - scoreTrack = make(map[h.Coordinate]int) - bestCostToEnd = make(map[h.Coordinate]int) - + inp := h.StdinToCoordMap() start, _ := inp.FindFirst('S') end, _ := inp.FindFirst('E') - inp.Put(start, '.') - bestCostToEnd[end] = 0 - cd := CD{c: start, d: E} - solve(inp, cd, end, 0, []h.Coordinate{cd.c}) - best, ok := scoreTrack[end] - if !ok { - panic(errors.New("no path to end found")) - } - fmt.Println("Best Score:", best) - - bestSeats := make(map[h.Coordinate]byte) - for i := range bestPaths { - for j := range bestPaths[i] { - bestSeats[bestPaths[i][j]] = byte('0' + i) - } - } - printBestSeats(inp, bestSeats) - fmt.Println("Seats:", len(bestSeats)) + score, seats := bfs(inp, start, end) + fmt.Println("# Part 1") + fmt.Println("Minimum Score:", score) + fmt.Println("# Part 2") + fmt.Println("Best Seats:", seats) } -var bestPaths [][]h.Coordinate +func bfs(inp h.CoordByteMap, s, e h.Coordinate) (int, int) { + start := CD{c: s, d: E} + minScore := math.MaxInt + queue := []State{ + { + pos: start, + path: []h.Coordinate{}, + }, + } + visited := make(map[CD]int) + sizeToC := make(map[int][]h.Coordinate) -func solve(inp h.CoordByteMap, start CD, end h.Coordinate, score int, currPath []h.Coordinate) int { - // tryMove takes the cd that we're moving to and the cost to move there - // and returns the cost to get from that cd to the end - tryMove := func(c CD, cost int) int { - if inp.ContainsCoord(c.c) && inp.Get(c.c) != '#' { - // printPathMap(inp, c, end, currPath) - // poi := h.Coordinate{X: 3, Y: 10} - // time.Sleep(time.Second / 20) - nextScore := score + cost - v, ok := bestCostToEnd[c.c] - //if c.c.Equals(poi) { - // fmt.Println("Current Scoretrack[poi] =", v) - // fmt.Println("Next Score =", nextScore) - // time.Sleep(time.Second * 5) - //} - if !ok || v >= nextScore { - nxtPath := make([]h.Coordinate, len(currPath)) - copy(nxtPath, currPath) - nxtPath = append(nxtPath, c.c) + for len(queue) > 0 { + currState := queue[0] + queue = queue[1:] - // Check end conditions - if c.c.Equals(end) { - if !ok || v > nextScore { // New Best - fmt.Println("New Best Path") - scoreTrack[end] = nextScore - bestPaths = [][]h.Coordinate{nxtPath} - } else if v == nextScore { // Another Best Path - fmt.Println("Adding path to best paths") - bestPaths = append(bestPaths, nxtPath) - } - return cost + if currState.score > minScore { + continue + } + + if currState.pos.c.Equals(e) { + if currState.score <= minScore { + minScore = currState.score + sizeToC[minScore] = append(sizeToC[minScore], currState.path...) + } + continue + } + + for _, n := range currState.pos.GetNeighbors() { + if inp.Get(n.c) == '#' { + continue + } + score := currState.score + 1 + if currState.pos.d != n.d { + score += 1000 + } + if prev, ok := visited[n]; ok { + if prev < score { + continue } - // Not the end, but keep going - scoreTrack[c.c] = nextScore - solve(inp, c, end, nextScore, nxtPath) } - } - return h.MAX_INT - } - // Test forward - cost := tryMove(start.move(), 1) - // Test CW - if wrk := tryMove(start.turnCW().move(), 1001); wrk < cost { - cost = wrk - } - // Test CCW - if wrk := tryMove(start.turnCCW().move(), 1001); wrk < cost { - cost = wrk - } - bestCostToEnd[start.c] = cost - return cost -} + visited[n] = score -func printPathMap(inp h.CoordByteMap, loc CD, end h.Coordinate, path []h.Coordinate) { - fmt.Print(h.CLEAR_SCREEN) - wrk := inp.Copy() - wrk.ReplaceAll('.', ' ') - for i := range path { - wrk.Put(path[i], 'o') - } - wrk.Put(loc.c, loc.Byte()) - fmt.Println(wrk) - if v, ok := scoreTrack[end]; ok { - fmt.Printf("Best Score: %v (Paths: %d)\n", v, len(bestPaths)) - } else { - fmt.Println("Waiting on best score...") - } -} - -func printBestSeats(inp h.CoordByteMap, seats map[h.Coordinate]byte) { - fmt.Print(h.CLEAR_SCREEN) - wrk := inp.Copy() - wrk.ReplaceAll('.', ' ') - for k, v := range seats { - wrk.Put(k, byte(0+v)) - } - fmt.Println(wrk) -} - -/* -func part2(inpS []string) { - inp := h.StringSliceToCoordByteMap(inpS) - scoreTrack = make(map[h.Coordinate]int) - start, _ := inp.FindFirst('S') - end, _ := inp.FindFirst('E') - inp.Put(start, '.') - cd := CD{c: start, d: E} - lookForSeats(inp, cd, end, 0, []h.Coordinate{cd.c}) - _, ok := scoreTrack[end] - if !ok { - panic(errors.New("no path to end found")) - } - bestSeats := make(map[h.Coordinate]int) - for i := range bestPaths { - for j := range bestPaths[i] { - bestSeats[bestPaths[i][j]] = i + np := make([]h.Coordinate, len(currState.path)) + copy(np, currState.path) + queue = append(queue, State{ + pos: n, + path: append(np, n.c), + score: score, + }) } } - fmt.Println("Number of Seats:", len(bestSeats)) -} - - - -func coordInPath(c h.Coordinate, path []h.Coordinate) bool { - for i := range path { - if path[i].Equals(c) { - return true - } + seatMap := make(map[h.Coordinate]bool) + for _, p := range sizeToC[minScore] { + seatMap[p] = true } - return false -} - -func lookForSeats(inp h.CoordByteMap, start CD, end h.Coordinate, score int, path []h.Coordinate) int { - // Check for finished: - if start.c.Equals(end) { - v, ok := scoreTrack[end] - if !ok || v > score { - // We have a new best path - bestPaths = [][]h.Coordinate{path} - scoreTrack[end] = score - } else if scoreTrack[end] == score { - bestPaths = append(bestPaths, path) - } - return score - } - - // Helper func - canMoveTo := func(p h.Coordinate) bool { - return inp.ContainsCoord(p) && inp.Get(p) != '#' - } - // Keep moving: - tryMove := func(c CD, cost int) { - if canMoveTo(c.c) { - // Check if this path has alread gone through this spot - nxtPath := make([]h.Coordinate, len(path)) - copy(nxtPath, path) - if coordInPath(c.c, nxtPath) { - return - } - // Ok, now what's the best score we end up if we make this move? - nextScore := score + cost - n, ok := scoreTrack[c.c] - if !ok || n > nextScore { - lookForSeats(inp, c, end, nextScore, append(nxtPath, c.c)) + /* + for y := inp.TLY; y <= inp.BRY; y++ { + for x := inp.TLX; x <= inp.BRX; x++ { + c := h.Coordinate{X: x, Y: y} + if _, ok := seatMap[c]; ok { + fmt.Print("O") + } else { + fmt.Print(string(inp.Get(c))) + } } + fmt.Println() } - } - // Test forward - tryMove(start.move(), 1) - // Test CW - tryMove(start.turnCW().move(), 1001) - // Test CCW - tryMove(start.turnCCW().move(), 1001) - return scoreTrack[start.c] + */ + return minScore, len(seatMap) + 1 } -*/ + +var ( + N = h.Coordinate{X: 0, Y: -1} + E = h.Coordinate{X: 1, Y: 0} + S = h.Coordinate{X: 0, Y: 1} + W = h.Coordinate{X: -1, Y: 0} + Dirs = []h.Coordinate{N, E, S, W} +) type CD struct { - c h.Coordinate - d h.Coordinate + c, d h.Coordinate } -func (cd CD) move() CD { - return CD{ - c: cd.c.Add(cd.d), - d: cd.d, +func (cd CD) GetNeighbors() []CD { + var ret []CD + opp := h.Coordinate{X: -cd.d.X, Y: -cd.d.Y} + for _, dir := range Dirs { + if !dir.Equals(opp) { + ret = append(ret, CD{ + c: cd.c.Add(dir), + d: dir, + }) + } } + return ret } -func (cd CD) turnCW() CD { - var newD h.Coordinate - switch cd.d { - case N: - newD = E - case E: - newD = S - case S: - newD = W - case W: - newD = N - } - return CD{ - c: cd.c, - d: newD, - } -} - -func (cd CD) turnCCW() CD { - var newD h.Coordinate - switch cd.d { - case N: - newD = W - case E: - newD = N - case S: - newD = E - case W: - newD = S - } - return CD{ - c: cd.c, - d: newD, - } -} - -func (cd CD) Byte() byte { - switch cd.d { - case N: - return '^' - case E: - return '>' - case S: - return 'v' - case W: - return '<' - } - return '?' +type State struct { + pos CD + path []h.Coordinate + score int } diff --git a/2024/day16/main.go.bk b/2024/day16/main.go.bk new file mode 100644 index 0000000..e990d69 --- /dev/null +++ b/2024/day16/main.go.bk @@ -0,0 +1,276 @@ +package main + +import ( + "errors" + "fmt" + + h "git.bullercodeworks.com/brian/adventofcode/helpers" +) + +func main() { + inp := h.StdinToStringSlice() + part1(inp) + // fmt.Println() + // part2(inp) +} + +var ( + N = h.Coordinate{X: 0, Y: -1} + E = h.Coordinate{X: 1, Y: 0} + S = h.Coordinate{X: 0, Y: 1} + W = h.Coordinate{X: -1, Y: 0} +) + +var ( + scoreTrack map[h.Coordinate]int + bestCostToEnd map[h.Coordinate]int +) + +func part1(inpS []string) { + inp := h.StringSliceToCoordByteMap(inpS) + scoreTrack = make(map[h.Coordinate]int) + bestCostToEnd = make(map[h.Coordinate]int) + + start, _ := inp.FindFirst('S') + end, _ := inp.FindFirst('E') + inp.Put(start, '.') + bestCostToEnd[end] = 0 + + cd := CD{c: start, d: E} + solve(inp, cd, end, 0, []h.Coordinate{cd.c}) + best, ok := scoreTrack[end] + if !ok { + panic(errors.New("no path to end found")) + } + fmt.Println("Best Score:", best) + + bestSeats := make(map[h.Coordinate]byte) + for i := range bestPaths { + for j := range bestPaths[i] { + bestSeats[bestPaths[i][j]] = byte('0' + i) + } + } + printBestSeats(inp, bestSeats) + fmt.Println("Seats:", len(bestSeats)) +} + +var bestPaths [][]h.Coordinate + +func solve(inp h.CoordByteMap, start CD, end h.Coordinate, score int, currPath []h.Coordinate) int { + // tryMove takes the cd that we're moving to and the cost to move there + // and returns the cost to get from that cd to the end + tryMove := func(c CD, cost int) int { + if inp.ContainsCoord(c.c) && inp.Get(c.c) != '#' { + // printPathMap(inp, c, end, currPath) + // poi := h.Coordinate{X: 3, Y: 10} + // time.Sleep(time.Second / 20) + nextScore := score + cost + v, ok := bestCostToEnd[c.c] + //if c.c.Equals(poi) { + // fmt.Println("Current Scoretrack[poi] =", v) + // fmt.Println("Next Score =", nextScore) + // time.Sleep(time.Second * 5) + //} + if !ok || v >= nextScore { + nxtPath := make([]h.Coordinate, len(currPath)) + copy(nxtPath, currPath) + nxtPath = append(nxtPath, c.c) + + // Check end conditions + if c.c.Equals(end) { + if !ok || v > nextScore { // New Best + fmt.Println("New Best Path") + scoreTrack[end] = nextScore + bestPaths = [][]h.Coordinate{nxtPath} + } else if v == nextScore { // Another Best Path + fmt.Println("Adding path to best paths") + bestPaths = append(bestPaths, nxtPath) + } + return cost + } + // Not the end, but keep going + scoreTrack[c.c] = nextScore + solve(inp, c, end, nextScore, nxtPath) + } + } + return h.MAX_INT + } + // Test forward + cost := tryMove(start.move(), 1) + // Test CW + if wrk := tryMove(start.turnCW().move(), 1001); wrk < cost { + cost = wrk + } + // Test CCW + if wrk := tryMove(start.turnCCW().move(), 1001); wrk < cost { + cost = wrk + } + bestCostToEnd[start.c] = cost + return cost +} + +func printPathMap(inp h.CoordByteMap, loc CD, end h.Coordinate, path []h.Coordinate) { + fmt.Print(h.CLEAR_SCREEN) + wrk := inp.Copy() + wrk.ReplaceAll('.', ' ') + for i := range path { + wrk.Put(path[i], 'o') + } + wrk.Put(loc.c, loc.Byte()) + fmt.Println(wrk) + if v, ok := scoreTrack[end]; ok { + fmt.Printf("Best Score: %v (Paths: %d)\n", v, len(bestPaths)) + } else { + fmt.Println("Waiting on best score...") + } +} + +func printBestSeats(inp h.CoordByteMap, seats map[h.Coordinate]byte) { + fmt.Print(h.CLEAR_SCREEN) + wrk := inp.Copy() + wrk.ReplaceAll('.', ' ') + for k, v := range seats { + wrk.Put(k, byte(0+v)) + } + fmt.Println(wrk) +} + +/* +func part2(inpS []string) { + inp := h.StringSliceToCoordByteMap(inpS) + scoreTrack = make(map[h.Coordinate]int) + start, _ := inp.FindFirst('S') + end, _ := inp.FindFirst('E') + inp.Put(start, '.') + cd := CD{c: start, d: E} + lookForSeats(inp, cd, end, 0, []h.Coordinate{cd.c}) + _, ok := scoreTrack[end] + if !ok { + panic(errors.New("no path to end found")) + } + bestSeats := make(map[h.Coordinate]int) + for i := range bestPaths { + for j := range bestPaths[i] { + bestSeats[bestPaths[i][j]] = i + } + } + fmt.Println("Number of Seats:", len(bestSeats)) +} + + + +func coordInPath(c h.Coordinate, path []h.Coordinate) bool { + for i := range path { + if path[i].Equals(c) { + return true + } + } + return false +} + +func lookForSeats(inp h.CoordByteMap, start CD, end h.Coordinate, score int, path []h.Coordinate) int { + // Check for finished: + if start.c.Equals(end) { + v, ok := scoreTrack[end] + if !ok || v > score { + // We have a new best path + bestPaths = [][]h.Coordinate{path} + scoreTrack[end] = score + } else if scoreTrack[end] == score { + bestPaths = append(bestPaths, path) + } + return score + } + + // Helper func + canMoveTo := func(p h.Coordinate) bool { + return inp.ContainsCoord(p) && inp.Get(p) != '#' + } + // Keep moving: + tryMove := func(c CD, cost int) { + if canMoveTo(c.c) { + // Check if this path has alread gone through this spot + nxtPath := make([]h.Coordinate, len(path)) + copy(nxtPath, path) + if coordInPath(c.c, nxtPath) { + return + } + // Ok, now what's the best score we end up if we make this move? + nextScore := score + cost + n, ok := scoreTrack[c.c] + if !ok || n > nextScore { + lookForSeats(inp, c, end, nextScore, append(nxtPath, c.c)) + } + } + } + // Test forward + tryMove(start.move(), 1) + // Test CW + tryMove(start.turnCW().move(), 1001) + // Test CCW + tryMove(start.turnCCW().move(), 1001) + return scoreTrack[start.c] +} +*/ + +type CD struct { + c h.Coordinate + d h.Coordinate +} + +func (cd CD) move() CD { + return CD{ + c: cd.c.Add(cd.d), + d: cd.d, + } +} + +func (cd CD) turnCW() CD { + var newD h.Coordinate + switch cd.d { + case N: + newD = E + case E: + newD = S + case S: + newD = W + case W: + newD = N + } + return CD{ + c: cd.c, + d: newD, + } +} + +func (cd CD) turnCCW() CD { + var newD h.Coordinate + switch cd.d { + case N: + newD = W + case E: + newD = N + case S: + newD = E + case W: + newD = S + } + return CD{ + c: cd.c, + d: newD, + } +} + +func (cd CD) Byte() byte { + switch cd.d { + case N: + return '^' + case E: + return '>' + case S: + return 'v' + case W: + return '<' + } + return '?' +}