day20 part1, dijkstras search with portals/jumps

This commit is contained in:
Alex Chao
2020-08-07 16:25:20 -04:00
parent 765b74e940
commit cd12e1a405
4 changed files with 408 additions and 3 deletions
+1 -1
View File
@@ -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
+195
View File
@@ -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
}
+210
View File
@@ -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
View File
@@ -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")
}