mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
day20 part1, dijkstras search with portals/jumps
This commit is contained in:
@@ -25,4 +25,4 @@ Day | Name | Type of Algo & Notes
|
||||
16 | Flawed Frequency Transmission | - Some really weird, contrived (as if this whole thing isn't) phase calculator?.. <br> - part2 broke my brain. Optimally calculate subsets by __precalculating running sums__ (linear), then getting subsets by subtracting two of the precalculated running sums. i.e. sub[2:4] = sub[0:4] - sub[0:2] <br> - And also switching pattern recognition to make calculating a particular output O(nlogn)...
|
||||
17 | Set and Forget | More Intcode... <br> Robot walking around a scaffolding... Fairly similar to previous algos, and a 2D grid traversal again <br> - I feel like I cheated part2 by finding the pattern by eye after printing what the 2D grid looks like. Then it was a matter of giving the intcode computer the corresponding inputs (in a weird format). <br> - Good example of __iterative approaches being better than recursive approaches__ because the recursive approach in "continuous video mode" causes a stack overflow very quickly
|
||||
18 | Many-Worlds Interpretation | - Oof, this is the hardest algo so far I think... Combination of __Best Pathfinding Algo__ (going to use a BFS/Dijkstra's) and a weighted __Graph Traversal__ with "locked" edges <br> - I toyed with the idea of using some kind of bitmap to track keys that were found, but ditched it so I wouldn't have to write my own wrapper around bitwise logic... Time complexity comparisons are roughly similar for comparing two maps as comparing two sets of bits, the space complexity is probably much better for 27-bits (@ and a-z) compared to a hashmap, but OH WELL. <br> - The graph traversal required memoizing in order to run in any reasonable amount of time. <br> - My part 2 assumptions would not work for every example which is unfortunate :/ I assumed that within a particular quadrant, if the quadrant does not have the key for a door, just remove the door.
|
||||
19 | Tractor Beam | - Another Intcode outputting to a 2D grid <br> - __THERE NEEDS TO BE A CONVENTION FOR X AND Y WHEN WORKING WITH 2D GRIDS__ This is awful, should it be mathematical? Should it represent rows and columns? Who knows?! <br>
|
||||
19 | Tractor Beam | - Another Intcode outputting to a 2D grid <br> - __THERE NEEDS TO BE A CONVENTION FOR X AND Y WHEN WORKING WITH 2D GRIDS__ This is awful, should it be mathematical? Should it represent rows and columns? Who knows?! <br> - Little geometry (drawing squares) for part2 but nothing crazy
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"adventofcode/util"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// large value to act as "infinite distance away" when setting up a dijkstra grid
|
||||
const bigSafeInt = 1 << 30 // 2^30
|
||||
|
||||
func main() {
|
||||
input := util.ReadFile("../input.txt")
|
||||
lines := strings.Split(string(input), "\n")
|
||||
grid := make([][]string, len(lines))
|
||||
for i, v := range lines {
|
||||
grid[i] = strings.Split(v, "")
|
||||
|
||||
// * Uncomment to print the input
|
||||
fmt.Println(grid[i])
|
||||
}
|
||||
|
||||
dijkstra := MakeDijkstraGrid(grid)
|
||||
// fmt.Println("initial dijkstra queue", dijkstra.queue)
|
||||
|
||||
for !dijkstra.handleFrontOfQueue() {
|
||||
// * watch the queue grow and shrink
|
||||
fmt.Println(" Queue", dijkstra.queue)
|
||||
}
|
||||
fmt.Println("Final Queue", dijkstra.queue)
|
||||
|
||||
fmt.Println("Distance to ZZ portal", dijkstra.grid[dijkstra.finishCoordinates[0]][dijkstra.finishCoordinates[1]].distance)
|
||||
}
|
||||
|
||||
// Dijkstra struct stores the 2D grid of nodes and a queue of next points to check
|
||||
// and a portal map to add jumps to the queue
|
||||
type Dijkstra struct {
|
||||
grid [][]*Node
|
||||
queue [][2]int
|
||||
portalMap map[string][2]int // map <portalName><originRow><originCol> to [2]int{<destinationRow><destinationCol>}
|
||||
startCoordinates [2]int
|
||||
finishCoordinates [2]int
|
||||
}
|
||||
|
||||
// Node data type is custom built for this algo, i.e. also stores if this is a portal cell
|
||||
type Node struct {
|
||||
value string
|
||||
distance int
|
||||
portalName string // <portalCharacters><row><col>, will be used to jump to other indexes
|
||||
jumpCoordinates [2]int // coordinates of its paired portal (if applicable)
|
||||
}
|
||||
|
||||
// MakeDijkstraGrid does just that
|
||||
func MakeDijkstraGrid(inputGrid [][]string) *Dijkstra {
|
||||
dijkstra := Dijkstra{}
|
||||
portalMapHelper := make(map[string][2]int)
|
||||
|
||||
grid := make([][]*Node, len(inputGrid)-4)
|
||||
// iterate starting at 2,2 to skip the top and left and end len-2 to skip bottom & right
|
||||
for row := 2; row < len(inputGrid)-2; row++ {
|
||||
grid[row-2] = make([]*Node, len(inputGrid[0])-4)
|
||||
for col := 2; col < len(inputGrid[0])-2; col++ {
|
||||
// make a node for each cell
|
||||
switch value := inputGrid[row][col]; value {
|
||||
case "#": // wall
|
||||
grid[row-2][col-2] = &Node{"#", bigSafeInt, "", [2]int{0, 0}}
|
||||
// if this is a hallway node, use a helper function to determine if there this is a portal
|
||||
case ".": // hallway
|
||||
hallwayNode := &Node{
|
||||
value: ".",
|
||||
distance: bigSafeInt,
|
||||
portalName: "",
|
||||
jumpCoordinates: [2]int{0, 0},
|
||||
}
|
||||
portalName := getPortalName(inputGrid, row, col)
|
||||
if len(portalName) != 0 {
|
||||
// assign portal name for this node
|
||||
hallwayNode.portalName = portalName
|
||||
|
||||
// generatine the portal maps for each node is a pain...
|
||||
// if this is portal's pair hasn't been found yet (i.e. equal to zero value of [2]int), add it to a map
|
||||
if pairedPortal := portalMapHelper[portalName]; pairedPortal == [2]int{0, 0} {
|
||||
portalMapHelper[portalName] = [2]int{row - 2, col - 2}
|
||||
} else {
|
||||
// else it has been found, set the jumpCoordinates on this node to pair's coords
|
||||
hallwayNode.jumpCoordinates = pairedPortal
|
||||
// set its pair's jumpCoordinates to this node's coords
|
||||
grid[pairedPortal[0]][pairedPortal[1]].jumpCoordinates = [2]int{row - 2, col - 2}
|
||||
}
|
||||
}
|
||||
grid[row-2][col-2] = hallwayNode
|
||||
// if it is AA, update the distance of this node to zero, initialize queue
|
||||
if portalName == "AA" {
|
||||
// !! unused
|
||||
dijkstra.startCoordinates = [2]int{row - 2, col - 2}
|
||||
|
||||
hallwayNode.distance = 0
|
||||
dijkstra.queue = [][2]int{
|
||||
[2]int{row - 2, col - 2},
|
||||
}
|
||||
}
|
||||
// if end portal, set finish coordinates
|
||||
if portalName == "ZZ" {
|
||||
dijkstra.finishCoordinates = [2]int{row - 2, col - 2}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set grid field
|
||||
dijkstra.grid = grid
|
||||
|
||||
return &dijkstra
|
||||
}
|
||||
|
||||
// helper function to run in 4 directions and see if any of them are a capital letter
|
||||
// if that's true, then grab the portal name in that direction and return it (two char string)
|
||||
func getPortalName(grid [][]string, row, col int) string {
|
||||
// NOTE I'm hard coding directions
|
||||
leftTwo := grid[row][col-2] + grid[row][col-1]
|
||||
rightTwo := grid[row][col+1] + grid[row][col+2]
|
||||
upTwo := grid[row-2][col] + grid[row-1][col]
|
||||
downTwo := grid[row+1][col] + grid[row+2][col]
|
||||
|
||||
isPortalString := func(str string) bool {
|
||||
ascii1 := str[0] - 'A'
|
||||
ascii2 := str[1] - 'A'
|
||||
|
||||
if ascii1 >= 0 && ascii1 < 26 && ascii2 >= 0 && ascii2 < 26 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// if both characters are capital letters
|
||||
switch {
|
||||
case isPortalString(leftTwo):
|
||||
return leftTwo
|
||||
case isPortalString(rightTwo):
|
||||
return rightTwo
|
||||
case isPortalString(upTwo):
|
||||
return upTwo
|
||||
case isPortalString(downTwo):
|
||||
return downTwo
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// returns true if the queue is empty OR the ZZ portal has been reached
|
||||
func (dijkstra *Dijkstra) handleFrontOfQueue() (done bool) {
|
||||
dRow := [4]int{0, 0, -1, 1}
|
||||
dCol := [4]int{-1, 1, 0, 0}
|
||||
|
||||
row, col := dijkstra.queue[0][0], dijkstra.queue[0][1]
|
||||
currentNode := dijkstra.grid[row][col]
|
||||
|
||||
if currentNode.portalName == "ZZ" {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
nextRow, nextCol := row+dRow[i], col+dCol[i]
|
||||
isInbounds := nextRow >= 0 && nextRow < len(dijkstra.grid) && nextCol >= 0 && nextCol < len(dijkstra.grid[0])
|
||||
if isInbounds {
|
||||
// if the nextNode is a hallway & has not been traveled to yet
|
||||
if nextNode := dijkstra.grid[nextRow][nextCol]; nextNode != nil && nextNode.value == "." && nextNode.distance == bigSafeInt {
|
||||
// update the distance of the nextNode
|
||||
nextNode.distance = currentNode.distance + 1
|
||||
// add its coordinates to the queue
|
||||
dijkstra.queue = append(dijkstra.queue, [2]int{nextRow, nextCol})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if a portal jump is possible!
|
||||
if currentNode.portalName != "" {
|
||||
// find coordinates to jump to and the node itself
|
||||
jumpRow := currentNode.jumpCoordinates[0]
|
||||
jumpCol := currentNode.jumpCoordinates[1]
|
||||
jumpNode := dijkstra.grid[jumpRow][jumpCol]
|
||||
|
||||
// update distance
|
||||
jumpNode.distance = currentNode.distance + 1
|
||||
// add to queue
|
||||
dijkstra.queue = append(dijkstra.queue, currentNode.jumpCoordinates)
|
||||
}
|
||||
|
||||
// dequeue, return true if queue is now empty
|
||||
dijkstra.queue = dijkstra.queue[1:]
|
||||
if len(dijkstra.queue) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"adventofcode/util"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// large value to act as "infinite distance away" when setting up a dijkstra grid
|
||||
const bigSafeInt = 1 << 30 // 2^30
|
||||
|
||||
func main() {
|
||||
input := util.ReadFile("../input.txt")
|
||||
lines := strings.Split(string(input), "\n")
|
||||
grid := make([][]string, len(lines))
|
||||
for i, v := range lines {
|
||||
grid[i] = strings.Split(v, "")
|
||||
|
||||
// * Uncomment to print the input
|
||||
fmt.Println(grid[i])
|
||||
}
|
||||
|
||||
dijkstra := MakeDijkstraGrid(grid)
|
||||
fmt.Println("initial dijkstra queue", dijkstra.queue)
|
||||
|
||||
}
|
||||
|
||||
// Dijkstra struct stores the 2D grid of nodes and a queue of next points to check
|
||||
// and a portal map to add jumps to the queue
|
||||
type Dijkstra struct {
|
||||
rawInput [][]string // stores the raw 2D grid to be passed into a Layer factory
|
||||
layers []*Layer
|
||||
queue [][3]int
|
||||
portalMap map[string][2]int // map to pass to Layer factory
|
||||
startCoordinates [2]int // coordinates on first layer
|
||||
finishCoordinates [2]int // coordinates on first layer
|
||||
}
|
||||
|
||||
// Layer is a single layer of the 3D maze
|
||||
// assuming the maze grows downwards
|
||||
type Layer struct {
|
||||
grid [][]*Node
|
||||
layerIndex int // ground level == 0, one down == 1, two down == 2, etc.
|
||||
portalMap map[string][3]int // jumpLayer, jumpRow, jumpCol
|
||||
}
|
||||
|
||||
// Node data type is custom built for this algo, i.e. also stores if this is a portal cell
|
||||
type Node struct {
|
||||
value string
|
||||
distance int
|
||||
portalName string // <portalCharacters><row><col>, will be used to jump to other indexes
|
||||
jumpCoordinates [2]int // coordinates of its paired portal (if applicable)
|
||||
}
|
||||
|
||||
// MakeDijkstraGrid does just that
|
||||
func MakeDijkstraGrid(inputGrid [][]string) *Dijkstra {
|
||||
dijkstra := Dijkstra{
|
||||
rawInput: inputGrid,
|
||||
layers: []*Layer{},
|
||||
queue: [][3]int{},
|
||||
portalMap: map[string][2]int{},
|
||||
startCoordinates: [2]int{},
|
||||
finishCoordinates: [2]int{},
|
||||
}
|
||||
portalMapHelper := make(map[string][2]int)
|
||||
|
||||
grid := make([][]*Node, len(inputGrid)-4)
|
||||
// iterate starting at 2,2 to skip the top and left and end len-2 to skip bottom & right
|
||||
for row := 2; row < len(inputGrid)-2; row++ {
|
||||
grid[row-2] = make([]*Node, len(inputGrid[0])-4)
|
||||
for col := 2; col < len(inputGrid[0])-2; col++ {
|
||||
// make a node for each cell
|
||||
switch value := inputGrid[row][col]; value {
|
||||
case "#": // wall
|
||||
grid[row-2][col-2] = &Node{"#", bigSafeInt, "", [2]int{0, 0}}
|
||||
// if this is a hallway node, use a helper function to determine if there this is a portal
|
||||
case ".": // hallway
|
||||
hallwayNode := &Node{
|
||||
value: ".",
|
||||
distance: bigSafeInt,
|
||||
portalName: "",
|
||||
jumpCoordinates: [2]int{0, 0},
|
||||
}
|
||||
portalName := getPortalName(inputGrid, row, col)
|
||||
if len(portalName) != 0 {
|
||||
// assign portal name for this node
|
||||
hallwayNode.portalName = portalName
|
||||
|
||||
// generatine the portal maps for each node is a pain...
|
||||
// if this is portal's pair hasn't been found yet (i.e. equal to zero value of [2]int), add it to a map
|
||||
if pairedPortal := portalMapHelper[portalName]; pairedPortal == [2]int{0, 0} {
|
||||
portalMapHelper[portalName] = [2]int{row - 2, col - 2}
|
||||
} else {
|
||||
// else it has been found, set the jumpCoordinates on this node to pair's coords
|
||||
hallwayNode.jumpCoordinates = pairedPortal
|
||||
// set its pair's jumpCoordinates to this node's coords
|
||||
grid[pairedPortal[0]][pairedPortal[1]].jumpCoordinates = [2]int{row - 2, col - 2}
|
||||
}
|
||||
}
|
||||
grid[row-2][col-2] = hallwayNode
|
||||
// if it is AA, update the distance of this node to zero, initialize queue
|
||||
if portalName == "AA" {
|
||||
// !! unused
|
||||
dijkstra.startCoordinates = [2]int{row - 2, col - 2}
|
||||
|
||||
hallwayNode.distance = 0
|
||||
dijkstra.queue = [][2]int{
|
||||
[2]int{row - 2, col - 2},
|
||||
}
|
||||
}
|
||||
// if end portal, set finish coordinates
|
||||
if portalName == "ZZ" {
|
||||
dijkstra.finishCoordinates = [2]int{row - 2, col - 2}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set grid field
|
||||
dijkstra.grid = grid
|
||||
|
||||
return &dijkstra
|
||||
}
|
||||
|
||||
// AddLayer will add a new layer to the dijkstra layers slice
|
||||
//
|
||||
func (dijkstra *Dijkstra) AddLayer() (layerCount int) {
|
||||
return len(dijkstra.layers)
|
||||
}
|
||||
|
||||
// helper function to run in 4 directions and see if any of them are a capital letter
|
||||
// if that's true, then grab the portal name in that direction and return it (two char string)
|
||||
func getPortalName(grid [][]string, row, col int) string {
|
||||
// NOTE I'm hard coding directions
|
||||
leftTwo := grid[row][col-2] + grid[row][col-1]
|
||||
rightTwo := grid[row][col+1] + grid[row][col+2]
|
||||
upTwo := grid[row-2][col] + grid[row-1][col]
|
||||
downTwo := grid[row+1][col] + grid[row+2][col]
|
||||
|
||||
isPortalString := func(str string) bool {
|
||||
ascii1 := str[0] - 'A'
|
||||
ascii2 := str[1] - 'A'
|
||||
|
||||
if ascii1 >= 0 && ascii1 < 26 && ascii2 >= 0 && ascii2 < 26 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// if both characters are capital letters
|
||||
switch {
|
||||
case isPortalString(leftTwo):
|
||||
return leftTwo
|
||||
case isPortalString(rightTwo):
|
||||
return rightTwo
|
||||
case isPortalString(upTwo):
|
||||
return upTwo
|
||||
case isPortalString(downTwo):
|
||||
return downTwo
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// returns true if the queue is empty OR the ZZ portal has been reached
|
||||
func (dijkstra *Dijkstra) handleFrontOfQueue() (done bool) {
|
||||
// dRow := [4]int{0, 0, -1, 1}
|
||||
// dCol := [4]int{-1, 1, 0, 0}
|
||||
|
||||
// row, col := dijkstra.queue[0][0], dijkstra.queue[0][1]
|
||||
// currentNode := dijkstra.grid[row][col]
|
||||
|
||||
// if currentNode.portalName == "ZZ" {
|
||||
// return true
|
||||
// }
|
||||
|
||||
// for i := 0; i < 4; i++ {
|
||||
// nextRow, nextCol := row+dRow[i], col+dCol[i]
|
||||
// isInbounds := nextRow >= 0 && nextRow < len(dijkstra.grid) && nextCol >= 0 && nextCol < len(dijkstra.grid[0])
|
||||
// if isInbounds {
|
||||
// // if the nextNode is a hallway & has not been traveled to yet
|
||||
// if nextNode := dijkstra.grid[nextRow][nextCol]; nextNode != nil && nextNode.value == "." && nextNode.distance == bigSafeInt {
|
||||
// // update the distance of the nextNode
|
||||
// nextNode.distance = currentNode.distance + 1
|
||||
// // add its coordinates to the queue
|
||||
// dijkstra.queue = append(dijkstra.queue, [2]int{nextRow, nextCol})
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // check if a portal jump is possible!
|
||||
// if currentNode.portalName != "" {
|
||||
// // find coordinates to jump to and the node itself
|
||||
// jumpRow := currentNode.jumpCoordinates[0]
|
||||
// jumpCol := currentNode.jumpCoordinates[1]
|
||||
// jumpNode := dijkstra.grid[jumpRow][jumpCol]
|
||||
|
||||
// // update distance
|
||||
// jumpNode.distance = currentNode.distance + 1
|
||||
// // add to queue
|
||||
// dijkstra.queue = append(dijkstra.queue, currentNode.jumpCoordinates)
|
||||
// }
|
||||
|
||||
// // dequeue, return true if queue is now empty
|
||||
// dijkstra.queue = dijkstra.queue[1:]
|
||||
// if len(dijkstra.queue) == 0 {
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
}
|
||||
+2
-2
@@ -28,7 +28,7 @@ func ReadFile(pathFromCaller string) string {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// trim off new lines at end of input files
|
||||
// trim off new lines and tabs at end of input files
|
||||
strContent := string(content)
|
||||
return strings.TrimSpace(strContent)
|
||||
return strings.TrimRight(strContent, "\n")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user