mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
might be making a mistake starting 2018...
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#################################################################################
|
||||
#...........#.....#.#...........#.....#.#...#...#...........#...........#......u#
|
||||
###.#######.#.###.#.#.###.#######.###.#.#.#.###.#.#######.###.#####.###.###.###.#
|
||||
#...#.....#.#.#.#.#.....#.#.......#...#.#.#...#.#.#.....#.........#...#...#...#.#
|
||||
#.#####I#.#.#.#.#.#######.#.#######.###.#.###.#.#.#####.#############.###.#####.#
|
||||
#.....#.#.#.#.#...........#.....#.#.....#...#.#.#.......#.....#.....#...#...#...#
|
||||
#.###.#.#.#.#.#################.#.#####.#.#.#.#J#######.#.#.#.#.###.###.###.#.#.#
|
||||
#...#.#.#.....#.....#...........#.#.....#.#.#.#...#.....#.#.#.#.#.....#.#...Y.#.#
|
||||
#####.#########.###.#.###########.#.#######.#.#.#.#.#####.#H#.#.#####.#.#######.#
|
||||
#...#...#.......#...#...#...#.....#.....#...#...#.#...#.#.#.#.#...#...#.....#...#
|
||||
#.#.###.#.#######.#####.#.#.#.###.#####.#.#######.###.#.#.#.#####.###.###.#.#####
|
||||
#.#.....#.#..k#..z......#.#.#...#.....#.#.....#.....#.#...#.#...#...#...#.#.....#
|
||||
#.#####.#.#.#.###########.#####.#.#####.#.###.#######.#.###.#.#.###.###.#######.#
|
||||
#.....#l#.R.#.....#.......#.....#...#...#...#.....#...#.#...#.#.....#...........#
|
||||
#.###.#.#######.#.###.#.###.#######.#.#####.#####.#.#####.###.###.#############.#
|
||||
#.#.#.#...#...#.#...#.#...#.#.....#.#...#.#.#.#...#...#...#...#...#...#.....#...#
|
||||
#.#.#.###.#.#.#####.#.###.#.#.#.###.###.#.#.#.#.#####.#.###.#.#####.#.#.###.#.###
|
||||
#...#.#...#.#.......#...#...#.#d..#.#...#..q..#...#...#...#.#.#.....#...Z.#r#.#.#
|
||||
###.#.###.#V###########.#####.###.#.#.###########O#.#####.#.###.###########.#.#.#
|
||||
#...#...#.#........f..#.#.....#...#.#.#.#.......#.#.#.....#.........#.......#...#
|
||||
#######.#############.#.#######.###.#.#.#.#####.#.#.#.###.#########.#.#########.#
|
||||
#.......#...........#.#.......#...#...#.#...#.#.#.#...#.#...#...#...#...#.#..e..#
|
||||
#.#######.###.#.#############.###.#####.###.#.#.#.#####.###.###.###.###.#.#.#####
|
||||
#p........#.#.#.#.......#...#...#.....#.#...#.#.#.#.......#...#.F.#...#.#...#.K.#
|
||||
#.#########.#.###.#.###.#.#.###.###.#.#.#.###.#.#.#N#####.###.###.#####.#####.#.#
|
||||
#...#.......#.....#...#.#.#.....#...#...#.#.......#.#...#...#...#.....#.......#.#
|
||||
###.#.###.###########.###.#######.#####.#.#########.#.#####.###.#####.#####.###.#
|
||||
#.#.#...#...........#.#...#.....#...S.#.#.#...#.......#...#...#.....#.....#...#.#
|
||||
#.#.###########.###.#.#.###.###.#####.#.#.#.#.#.#######.#.###.#####.#####.###.#.#
|
||||
#...#.........#.#...#...#...#.#.......#.#...#...#...#...#...#..x..#.#...#...#.#.#
|
||||
#.###.#######.#.#########.#.#.#########.#.#######.#.#.#####.###.###.#.#####.###.#
|
||||
#.....#.....#.#.......#...#.#.#.......#.#.#.......#.#.#.......#.#...#.#...#...#.#
|
||||
#########.###.#.###.###.###.#.#.#####.#.#.#######.#.#.#######.#.#.###.#.#.###.#.#
|
||||
#.....#...#...#.#.#.#...#...#...#..t#...#...#.....#.#.....#.....#...#...#...#..v#
|
||||
#.###.#.###.###.#.#.#.###.###.#####.#######.#.#####.#####.###.#####.#####.#######
|
||||
#.#.....#...#.....#.....#.#...........#.#...#.#...#.#...#...#.#...#.....#.......#
|
||||
#.#######.#######.#####.#.###########.#.#.###.#.###.#.#.###.###.#.#####.#####.#.#
|
||||
#.......#.#.....#.#...#.#.......#.#...#.#.....#...#...#...#.....#.....#.#...#.#.#
|
||||
#.#####.#.#X###.###.#.#########.#.#.###.#######.#.#######.###########.#.#.#P###.#
|
||||
#.....#.....#.......#...........#...............#.........T....g....#.....#.....#
|
||||
#######################################.@.#######################################
|
||||
#..y#...#.................#...#...................#.....#.........#.....#...#...#
|
||||
#.###.#.#.#########.#.#####E#.#####.#.#.#.###.#####.###.###.#.###.###.#.#.#.#.#.#
|
||||
#.#...#...#...#.#.U.#.#.....#.#...#.#.#.#.#.#...#...#.#...#.#...#.#...#.#.#.#.#.#
|
||||
#.#.#######.#.#.#.#####.#####.#.#.###.#.#.#.###.#.###.###.#####.#.#.###.#A#.#.#.#
|
||||
#.#...#...C.#...#.........#..a#.#...#.#.#.....#...#.#...#.#.....#.#.#...#.#b..#.#
|
||||
#.###.#.#######.###########.###.###.#.#.#####.#####.#.#.#.#.#####.#.#.###.#####.#
|
||||
#.....#.#.......#.#...B...#.#...#.#...#.#.#...#...#...#.#.#.#...#...#...#.#.....#
|
||||
#.#####.#.#######.#.#####.#.#.#.#.#####.#.#.###.#.#####.#.#.#.#.#######.#.#.#####
|
||||
#.W.....#.#...#...#...#...#.#.#.#.....#.#.#.....#.......#.#...#.#.....#...#...#.#
|
||||
#########.#.#.###.###.#.###.#.#.#.#.###.#.#############.#.###.#.###.#.#.#####.#.#
|
||||
#.......#...#...#.#...#.#...#.#.#.#.....#.....#.......#.#...#.#...#.#.#.#...#...#
|
||||
#####.#.#######.#.#.###.###.#.#.#.#######.#####.#.#####.###.#.###.###.#.#.#.###.#
|
||||
#.....#.......#.#.#.#...#...#.#.#.....#.#.....#.#.........#.#.#.#...#...#.#c#...#
|
||||
#.#########.###.#.#.###.#.#####.#####.#.#.###.#.###########.#.#.###.#.###.#.#####
|
||||
#.#.....#...#...#.#...#...#.G.#.#...#.#.#...#...#...#...#...#...#.#.#...#.#.....#
|
||||
#.#.#####.###.###.###.#####.#.#.#.#.#.#.###.#####.###.#.#.#####.#.#.#####.#####.#
|
||||
#.#.....#...#.#.....#.#.....#...#.#.#.#.#.#...#.....#.#...#...#...#..w......#.#.#
|
||||
#.#.###.###.#.###.###.###.#########.#.#.#.###.#.###.#.###.#.#.###.#########.#.#.#
|
||||
#.#...#...#.#...#.#...#...#.........#.#.#...#.#.#.#.#.#...#.#...#.#...#...#...#.#
|
||||
#.#######.#.###.#.#.###.#######.#####.#.#.#.#.#.#.#.#.#####.###.###.#.#.#.#####.#
|
||||
#.......#.#.#...#.#...#.#.......#.....#.#.#.#.#...#.#...#...#.#.....#...#...#...#
|
||||
#.#####.#.#.#.###.#####.###.#.###.#####.#.#.#.#####.###.#.###.#######.#####.#.#.#
|
||||
#.#...#.#...#.#...#.....#...#.#...#.....#.#.#.....#...#...#...#.....#.....#.#.#.#
|
||||
#.#.#.#.#####.#.#.#.#####.###.#.#####.#.#.#######.###.#####.###.###.#######.#.###
|
||||
#.#.#...#.....#.#...#...#...#.#....s..#.#.....#...#.....#...#...#.........#.#...#
|
||||
#.#.###.#.#####.#####.#.#.#.#.#########.#.###.#.###.###.#.###.#.#########.#.###.#
|
||||
#.#..m#.#.....#...#...#...#.#.#.......#.#.#.#.#.#...#...#...#.#.#...#...#...#..i#
|
||||
#.###.#######.#.#.#.#######.#.#####.#.#.#.#.#.#.#.###.#####.#.###.#.#.#######.#.#
|
||||
#...#.........#.#.#.......#.#...#...#.#.#.#.#...#.#...#...#.#.....#.#.......#.#.#
|
||||
###.#############.#######.#.###.#.#.###.#.#.#####.#.###.#.#.#.#####Q###.###M#.###
|
||||
#...#.....#.......#..j#...#.#.....#.#...#...#.....#...#.#.#.#.#.....#...#...#...#
|
||||
#.#####.#.###.#######.#.###.#########.#####.#.#.#####.#.#.#.###.#####.###.###.#.#
|
||||
#...#...#.....#.....#...#.#.......#...#.#...#.#.#...#.#.#.#.....#.....#.#.#...#.#
|
||||
###.#.#########.###.#.###.#######.#.###.#.#####.#.#.#.#.###########.###.#.#####.#
|
||||
#.#.#.............#.#.#.....#....o#.#...#.......#.#.#.#...#.......#.#..h#...#...#
|
||||
#.#.###############.#.#####.#.###.#.###.###########.#.#.#.#.###.#.#.#.#.###.#.###
|
||||
#.#...#.........#...#.......#.#...#.D...#...#.......#.#.#...#...#...#.#...#.#...#
|
||||
#.###.#.#######.#.#########.#.#########.#.#.#.###.###.#######.#######.#.###.###L#
|
||||
#.......#.........#.........#..........n#.#.....#.............#.......#.........#
|
||||
#################################################################################
|
||||
@@ -0,0 +1,286 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"adventofcode/util"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := util.ReadFile("../input.txt")
|
||||
linesSli := strings.Split(input, "\n")
|
||||
|
||||
grid := make([][]string, len(linesSli)) // string might be excessive, but it will be easier to look at
|
||||
for i, line := range linesSli {
|
||||
grid[i] = strings.Split(line, "")
|
||||
}
|
||||
|
||||
// get all the coordinates of keys. will be used as starting points for dijkstra's searches
|
||||
keyToCoordinates := getCoordinatesOfKeys(grid)
|
||||
|
||||
// initialize Graph
|
||||
graph := MakeGraph()
|
||||
|
||||
// for every key, generate a new dijkstra grid where all distances are set to MAX SAFE INT, and the key's distance is set to 0
|
||||
for key, sourceCoordinates := range keyToCoordinates {
|
||||
// initialize a dijkstra grid for each key
|
||||
dijkstraGrid := MakeDijkstraGrid(grid, sourceCoordinates)
|
||||
|
||||
// step through grid until complete, handleFrontOfQueue returns true when the queue is empty...
|
||||
for !dijkstraGrid.handleFrontOfQueue() {
|
||||
}
|
||||
// small space optimization, overwrite the dijkstra queue b/c the underlying array is alive
|
||||
dijkstraGrid.queue = nil
|
||||
|
||||
// update graph for all edges from `key` to all other keys
|
||||
for destinationKey, destinationCoordinates := range keyToCoordinates {
|
||||
if key != destinationKey && destinationKey != "@" {
|
||||
row, col := destinationCoordinates[0], destinationCoordinates[1]
|
||||
// pass the key being pathed FROM and the dijkstraNode (found on the grid) of the key found
|
||||
graph.AddEdges(key, dijkstraGrid.grid[row][col])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dfsStartTimestamp := time.Now()
|
||||
// first off (recursive, memoized) dfs method on graph that finds minimum distance
|
||||
fmt.Printf("DFS result: %v\n\nFinished in %v\n\n", graph.dfsMinmumDistance(), time.Since(dfsStartTimestamp))
|
||||
}
|
||||
|
||||
// get a map of the coordinates of points of interests
|
||||
func getCoordinatesOfKeys(grid [][]string) map[string][2]int {
|
||||
pointsOfInterest := make(map[string][2]int)
|
||||
for row, rowSli := range grid {
|
||||
for col, cell := range rowSli {
|
||||
switch {
|
||||
case cell == "@":
|
||||
pointsOfInterest["@"] = [2]int{row, col}
|
||||
// typecase cell to its ASCII value
|
||||
case int(cell[0]) >= 'a' && int(cell[0]) <= 'z':
|
||||
pointsOfInterest[cell] = [2]int{row, col}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pointsOfInterest
|
||||
}
|
||||
|
||||
/********************************************************************************************
|
||||
DIJKSTRA GRID Code
|
||||
*********************************************************************************************/
|
||||
|
||||
// DijkstraGrid stores the grid itself and also the needed queue to traverse through the grid
|
||||
type DijkstraGrid struct {
|
||||
grid [][]*dijkstraNode
|
||||
queue [][2]int // coordinates to be traversed next
|
||||
}
|
||||
|
||||
// dijkstraNode is each cell within a DijkstraGrid.grid
|
||||
type dijkstraNode struct {
|
||||
value string // string of the floor type (key, door, floor?, wall)
|
||||
distance int // distance from the source
|
||||
keysFound map[string]bool // keys that have been run into
|
||||
keysNeeded map[string]bool // keys needed to get to this node, i.e. all doors passed thorugh
|
||||
seen bool
|
||||
}
|
||||
|
||||
// MakeDijkstraGrid initializes a 2D grid of dijkstraNodes with distances initialized to the max safe integer
|
||||
func MakeDijkstraGrid(grid [][]string, startCoords [2]int) *DijkstraGrid {
|
||||
finalGrid := make([][]*dijkstraNode, len(grid))
|
||||
startKey := grid[startCoords[0]][startCoords[1]]
|
||||
for row, rowSli := range grid {
|
||||
// initialize the row's slice in the finalGrid
|
||||
finalGrid[row] = make([]*dijkstraNode, len(grid[0]))
|
||||
for col, cellString := range rowSli {
|
||||
finalGrid[row][col] = &dijkstraNode{
|
||||
value: cellString,
|
||||
distance: math.MaxInt32, // maximum safe integer, effectively 2^31 - 1
|
||||
keysFound: map[string]bool{startKey: true}, // initialize with the starting key
|
||||
keysNeeded: make(map[string]bool), // empty map for now
|
||||
seen: false, // initialize as false
|
||||
}
|
||||
// remove the "@" key because it's not a key... it's just the starting point
|
||||
delete(finalGrid[row][col].keysFound, "@")
|
||||
}
|
||||
}
|
||||
|
||||
// set properties of starting coordinate
|
||||
// distance = 0
|
||||
finalGrid[startCoords[0]][startCoords[1]].distance = 0
|
||||
finalGrid[startCoords[0]][startCoords[1]].seen = true
|
||||
queue := [][2]int{
|
||||
[2]int{startCoords[0], startCoords[1]},
|
||||
}
|
||||
return &DijkstraGrid{finalGrid, queue}
|
||||
}
|
||||
|
||||
var dRow [4]int = [4]int{0, 0, -1, 1}
|
||||
var dCol [4]int = [4]int{-1, 1, 0, 0}
|
||||
|
||||
// handleFrontOfQueue does just that, returns a true if queue is empty upon completion
|
||||
func (dijk *DijkstraGrid) handleFrontOfQueue() (queueIsEmpty bool) {
|
||||
row, col := dijk.queue[0][0], dijk.queue[0][1]
|
||||
cell := dijk.grid[row][col]
|
||||
|
||||
// mark as seen
|
||||
cell.seen = true
|
||||
|
||||
// if key is found, add to cell details
|
||||
if ascii := int(cell.value[0]) - 'a'; ascii >= 0 && ascii < 26 {
|
||||
cell.keysFound[cell.value] = true
|
||||
}
|
||||
|
||||
// if door is found, add to keysNeeded as a lowercase (easier comparison later)
|
||||
if ascii := int(cell.value[0]) - 'A'; ascii >= 0 && ascii < 26 {
|
||||
cell.keysNeeded[string('a'+ascii)] = true
|
||||
}
|
||||
|
||||
// push neighbors into queue IF not already seen and not walls
|
||||
for i := 0; i < 4; i++ {
|
||||
neighborRow, neighborCol := row+dRow[i], col+dCol[i]
|
||||
neighborCell := dijk.grid[neighborRow][neighborCol]
|
||||
if !neighborCell.seen && neighborCell.value != "#" {
|
||||
// add to queue
|
||||
dijk.queue = append(dijk.queue, [2]int{neighborRow, neighborCol})
|
||||
|
||||
// increment its distance by 1
|
||||
neighborCell.distance = cell.distance + 1
|
||||
|
||||
// NOTE manually copying these, otherwise they'll be pointing to the same underlying map pointer
|
||||
// copy keysFound (still carrying the same keys)
|
||||
for key := range cell.keysFound {
|
||||
neighborCell.keysFound[key] = true
|
||||
}
|
||||
|
||||
// copy keysNeeded as well
|
||||
for key := range cell.keysNeeded {
|
||||
neighborCell.keysNeeded[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dequeue by rescoping the slice
|
||||
dijk.queue = dijk.queue[1:]
|
||||
|
||||
// if queue is empty, there are no more paths to generate, return a true
|
||||
if len(dijk.queue) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/********************************************************************************************
|
||||
GRAPH Code
|
||||
*********************************************************************************************/
|
||||
|
||||
// Graph stores the edges between keys by storing all paths from a particular key to all other keys
|
||||
// all cells in this 2D MAP will be a dijkstraNode because those contain all the needed data
|
||||
// such as distance from last key, the keysNeeded to take this path
|
||||
type Graph struct {
|
||||
keysToKeys map[string]map[string]*dijkstraNode
|
||||
}
|
||||
|
||||
// MakeGraph initializes a Graph instance and returns a pointer to it
|
||||
func MakeGraph() *Graph {
|
||||
return &Graph{
|
||||
keysToKeys: make(map[string]map[string]*dijkstraNode),
|
||||
}
|
||||
}
|
||||
|
||||
// AddEdges takes in the key being pathed FROM, and the dijkstraNode of a key pathed TO
|
||||
// and adds that edge to the graph
|
||||
func (graph *Graph) AddEdges(startKey string, destinationNode *dijkstraNode) {
|
||||
// initialize this "row" of the map if it does not exist yet
|
||||
if graph.keysToKeys[startKey] == nil {
|
||||
graph.keysToKeys[startKey] = make(map[string]*dijkstraNode)
|
||||
}
|
||||
// add the destination node to the graph
|
||||
keyFound := destinationNode.value
|
||||
graph.keysToKeys[startKey][keyFound] = destinationNode
|
||||
}
|
||||
|
||||
func (graph *Graph) dfsMinmumDistance() int {
|
||||
// recursive function that dives through graph
|
||||
var traverse func(string, map[string]bool) int
|
||||
|
||||
// Implement cache for traverse function
|
||||
// caches the "<entry key>:<ordered keys found>" to resulting distance
|
||||
cache := map[string]int{}
|
||||
|
||||
// NOTE helper function that leverages the above cache
|
||||
// Traverse should return the minimum distance to a completion for these inputs
|
||||
// inputs are: 1. the entry point (the key to generate a path FROM)
|
||||
// 2. the keys that have been found so far
|
||||
traverse = func(entry string, keysFound map[string]bool) int {
|
||||
shortestFromThisNode := math.MaxInt32
|
||||
|
||||
cacheKey := makeCacheKey(entry, keysFound, len(graph.keysToKeys)-1)
|
||||
|
||||
// cache hit
|
||||
if cacheVal, found := cache[cacheKey]; found {
|
||||
return cacheVal
|
||||
}
|
||||
|
||||
// base case: all keys found, no more distance to be traveled, i.e. zero
|
||||
if len(keysFound) == len(graph.keysToKeys)-1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// iterate over all possible destination nodes.
|
||||
nextEdges := graph.keysToKeys[entry]
|
||||
for nextKey, node := range nextEdges {
|
||||
// only traverse if key has not been found yet AND all needed keys have been collected already
|
||||
if !keysFound[nextKey] && haveNeededKeys(keysFound, node.keysNeeded) {
|
||||
// add the nextKey
|
||||
keysFound[nextKey] = true
|
||||
|
||||
// update the shortestFromThisNode if applicable
|
||||
distanceToEnd := node.distance + traverse(nextKey, keysFound)
|
||||
if distanceToEnd < shortestFromThisNode {
|
||||
shortestFromThisNode = distanceToEnd
|
||||
}
|
||||
|
||||
// backtrack - remove the key
|
||||
delete(keysFound, nextKey)
|
||||
}
|
||||
}
|
||||
|
||||
cache[cacheKey] = shortestFromThisNode
|
||||
return shortestFromThisNode
|
||||
}
|
||||
|
||||
// fire off initially
|
||||
return traverse("@", map[string]bool{})
|
||||
}
|
||||
|
||||
// helper function to generate a cache string key
|
||||
// cache string is of the form entryKey:allKeysToFind
|
||||
func makeCacheKey(entry string, keysFound map[string]bool, totalKeys int) string {
|
||||
// TODO: move this into the graph? to avoid making it 910238190382 times
|
||||
allKeys := make([]string, totalKeys)
|
||||
for i := 0; i < totalKeys; i++ {
|
||||
allKeys[i] = string(int('a') + i)
|
||||
}
|
||||
|
||||
cacheKey := entry
|
||||
// generate
|
||||
for i := 0; i < totalKeys; i++ {
|
||||
char := string(int('a') + i)
|
||||
if !keysFound[char] {
|
||||
cacheKey += char
|
||||
}
|
||||
}
|
||||
|
||||
return cacheKey
|
||||
}
|
||||
|
||||
// simple helper function to make sure that all keysNeeded are in keysFound
|
||||
func haveNeededKeys(keysFound, keysNeeded map[string]bool) bool {
|
||||
for key := range keysNeeded {
|
||||
if !keysFound[key] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"adventofcode/util"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := util.ReadFile("../input.txt")
|
||||
linesSli := strings.Split(input, "\n")
|
||||
|
||||
// generate four quadrants
|
||||
quad1, quad2, quad3, quad4 := generateQuadrants(linesSli)
|
||||
|
||||
// handle an individual grid, sum answers together to print
|
||||
one := handleGrid(quad1)
|
||||
two := handleGrid(quad2)
|
||||
three := handleGrid(quad3)
|
||||
four := handleGrid(quad4)
|
||||
|
||||
fmt.Printf("Sum for four robots is %v\n", one+two+three+four)
|
||||
}
|
||||
|
||||
// Helper function to do all the things to a grid...
|
||||
func handleGrid(grid [][]string) int {
|
||||
removeDoorsWithoutKeys(grid)
|
||||
|
||||
keysToCoordinates := getCoordinatesOfKeys(grid)
|
||||
|
||||
graph := MakeGraph()
|
||||
|
||||
for key, sourceCoordinates := range keysToCoordinates {
|
||||
dijkstraGrid := MakeDijkstraGrid(grid, sourceCoordinates)
|
||||
|
||||
// step through grid until complete, handleFrontOfQueue returns true when the queue is empty...
|
||||
for !dijkstraGrid.handleFrontOfQueue() {
|
||||
}
|
||||
// small space optimization, overwrite the dijkstra queue b/c the underlying array is alive
|
||||
dijkstraGrid.queue = nil
|
||||
|
||||
// update graph for all edges from `key` to all other keys
|
||||
for otherKey, destinationCoordinates := range keysToCoordinates {
|
||||
if key != otherKey && otherKey != "@" {
|
||||
row, col := destinationCoordinates[0], destinationCoordinates[1]
|
||||
// pass the key being pathed FROM and the dijkstraNode (found on the grid) of the key found
|
||||
graph.AddEdges(key, dijkstraGrid.grid[row][col])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then concurrently write back to the result chan
|
||||
return graph.dfsMinmumDistance()
|
||||
}
|
||||
|
||||
// get a map of the coordinates of points of interests
|
||||
func getCoordinatesOfKeys(grid [][]string) map[string][2]int {
|
||||
pointsOfInterest := make(map[string][2]int)
|
||||
for row, rowSli := range grid {
|
||||
for col, cell := range rowSli {
|
||||
switch {
|
||||
case cell == "@":
|
||||
pointsOfInterest["@"] = [2]int{row, col}
|
||||
// typecase cell to its ASCII value
|
||||
case int(cell[0]) >= 'a' && int(cell[0]) <= 'z':
|
||||
pointsOfInterest[cell] = [2]int{row, col}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pointsOfInterest
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
DIJKSTRA GRID Code
|
||||
*****************************************************************************************/
|
||||
|
||||
// DijkstraGrid stores the grid itself and also the needed queue to traverse through the grid
|
||||
type DijkstraGrid struct {
|
||||
grid [][]*dijkstraNode
|
||||
queue [][2]int // coordinates to be traversed next
|
||||
}
|
||||
|
||||
// dijkstraNode is each cell within a DijkstraGrid.grid
|
||||
type dijkstraNode struct {
|
||||
value string // string of the floor type (key, door, floor?, wall)
|
||||
distance int // distance from the source
|
||||
keysFound map[string]bool // keys that have been run into
|
||||
keysNeeded map[string]bool // keys needed to get to this node, i.e. all doors passed thorugh
|
||||
seen bool
|
||||
}
|
||||
|
||||
// MakeDijkstraGrid initializes a 2D grid of dijkstraNodes with distances initialized to the max safe integer
|
||||
func MakeDijkstraGrid(grid [][]string, startCoords [2]int) *DijkstraGrid {
|
||||
finalGrid := make([][]*dijkstraNode, len(grid))
|
||||
startKey := grid[startCoords[0]][startCoords[1]]
|
||||
for row, rowSli := range grid {
|
||||
// initialize the row's slice in the finalGrid
|
||||
finalGrid[row] = make([]*dijkstraNode, len(grid[0]))
|
||||
for col, cellString := range rowSli {
|
||||
finalGrid[row][col] = &dijkstraNode{
|
||||
value: cellString,
|
||||
distance: math.MaxInt32, // maximum safe integer, effectively 2^31 - 1
|
||||
keysFound: map[string]bool{startKey: true}, // initialize with the starting key
|
||||
keysNeeded: make(map[string]bool), // empty map for now
|
||||
seen: false, // initialize as false
|
||||
}
|
||||
// remove the "@" key because it's not a key... it's just the starting point
|
||||
delete(finalGrid[row][col].keysFound, "@")
|
||||
}
|
||||
}
|
||||
|
||||
// set properties of starting coordinate
|
||||
// distance = 0
|
||||
finalGrid[startCoords[0]][startCoords[1]].distance = 0
|
||||
finalGrid[startCoords[0]][startCoords[1]].seen = true
|
||||
queue := [][2]int{
|
||||
[2]int{startCoords[0], startCoords[1]},
|
||||
}
|
||||
return &DijkstraGrid{finalGrid, queue}
|
||||
}
|
||||
|
||||
var dRow [4]int = [4]int{0, 0, -1, 1}
|
||||
var dCol [4]int = [4]int{-1, 1, 0, 0}
|
||||
|
||||
// handleFrontOfQueue does just that, returns a true if queue is empty upon completion
|
||||
func (dijk *DijkstraGrid) handleFrontOfQueue() (queueIsEmpty bool) {
|
||||
row, col := dijk.queue[0][0], dijk.queue[0][1]
|
||||
cell := dijk.grid[row][col]
|
||||
|
||||
// mark as seen
|
||||
cell.seen = true
|
||||
|
||||
// if key is found, add to cell details
|
||||
if ascii := int(cell.value[0]) - 'a'; ascii >= 0 && ascii < 26 {
|
||||
cell.keysFound[cell.value] = true
|
||||
}
|
||||
|
||||
// if door is found, add to keysNeeded as a lowercase (easier comparison later)
|
||||
if ascii := int(cell.value[0]) - 'A'; ascii >= 0 && ascii < 26 {
|
||||
cell.keysNeeded[string('a'+ascii)] = true
|
||||
}
|
||||
|
||||
// push neighbors into queue IF not already seen and not walls
|
||||
for i := 0; i < 4; i++ {
|
||||
neighborRow, neighborCol := row+dRow[i], col+dCol[i]
|
||||
neighborCell := dijk.grid[neighborRow][neighborCol]
|
||||
if !neighborCell.seen && neighborCell.value != "#" {
|
||||
// add to queue
|
||||
dijk.queue = append(dijk.queue, [2]int{neighborRow, neighborCol})
|
||||
|
||||
// increment its distance by 1
|
||||
neighborCell.distance = cell.distance + 1
|
||||
|
||||
// NOTE manually copying these, otherwise they'll be pointing to the same underlying map pointer
|
||||
// copy keysFound (still carrying the same keys)
|
||||
for key := range cell.keysFound {
|
||||
neighborCell.keysFound[key] = true
|
||||
}
|
||||
|
||||
// copy keysNeeded as well
|
||||
for key := range cell.keysNeeded {
|
||||
neighborCell.keysNeeded[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dequeue by rescoping the slice
|
||||
dijk.queue = dijk.queue[1:]
|
||||
|
||||
// if queue is empty, there are no more paths to generate, return a true
|
||||
if len(dijk.queue) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
GRAPH Code
|
||||
*****************************************************************************************/
|
||||
|
||||
// Graph stores the edges between keys by storing all paths from a particular key to all other keys
|
||||
// all cells in this 2D MAP will be a dijkstraNode because those contain all the needed data
|
||||
// such as distance from last key, the keysNeeded to take this path
|
||||
type Graph struct {
|
||||
keysToKeys map[string]map[string]*dijkstraNode
|
||||
allKeysNeeded map[string]bool
|
||||
}
|
||||
|
||||
// MakeGraph initializes a Graph instance and returns a pointer to it
|
||||
func MakeGraph() *Graph {
|
||||
return &Graph{
|
||||
keysToKeys: make(map[string]map[string]*dijkstraNode),
|
||||
allKeysNeeded: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// AddEdges takes in the key being pathed FROM, and the dijkstraNode of a key pathed TO
|
||||
// and adds that edge to the graph
|
||||
func (graph *Graph) AddEdges(startKey string, destinationNode *dijkstraNode) {
|
||||
// add startKey to the allKeysNeeded hashmap
|
||||
graph.allKeysNeeded[startKey] = true
|
||||
|
||||
// initialize this "row" of the map if it does not exist yet
|
||||
if graph.keysToKeys[startKey] == nil {
|
||||
graph.keysToKeys[startKey] = make(map[string]*dijkstraNode)
|
||||
}
|
||||
// add the destination node to the graph
|
||||
keyFound := destinationNode.value
|
||||
graph.keysToKeys[startKey][keyFound] = destinationNode
|
||||
}
|
||||
|
||||
func (graph *Graph) dfsMinmumDistance() int {
|
||||
// recursive function that dives through graph
|
||||
var traverse func(string, map[string]bool) int
|
||||
|
||||
// Implement cache for traverse function
|
||||
// caches the "<entry key>:<ordered keys found>" to resulting distance
|
||||
cache := map[string]int{}
|
||||
|
||||
// NOTE helper function that leverages the above cache
|
||||
// Traverse should return the minimum distance to a completion for these inputs
|
||||
// inputs are: 1. the entry point (the key to generate a path FROM)
|
||||
// 2. the keys that have been found so far
|
||||
traverse = func(entry string, keysFound map[string]bool) int {
|
||||
shortestFromThisNode := math.MaxInt32
|
||||
|
||||
cacheKey := makeCacheKey(entry, keysFound, graph.allKeysNeeded)
|
||||
|
||||
// cache hit
|
||||
if cacheVal, found := cache[cacheKey]; found {
|
||||
return cacheVal
|
||||
}
|
||||
|
||||
// base case: all keys found, no more distance to be traveled, i.e. zero
|
||||
if len(keysFound) == len(graph.keysToKeys)-1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// iterate over all possible destination nodes.
|
||||
nextEdges := graph.keysToKeys[entry]
|
||||
for nextKey, node := range nextEdges {
|
||||
// only traverse if key has not been found yet AND all needed keys have been collected already
|
||||
if !keysFound[nextKey] && haveNeededKeys(keysFound, node.keysNeeded) {
|
||||
// add the nextKey
|
||||
keysFound[nextKey] = true
|
||||
|
||||
// update the shortestFromThisNode if applicable
|
||||
distanceToEnd := node.distance + traverse(nextKey, keysFound)
|
||||
if distanceToEnd < shortestFromThisNode {
|
||||
shortestFromThisNode = distanceToEnd
|
||||
}
|
||||
|
||||
// backtrack - remove the key
|
||||
delete(keysFound, nextKey)
|
||||
}
|
||||
}
|
||||
|
||||
cache[cacheKey] = shortestFromThisNode
|
||||
return shortestFromThisNode
|
||||
}
|
||||
|
||||
// fire off initially
|
||||
return traverse("@", map[string]bool{})
|
||||
}
|
||||
|
||||
// helper function to generate a cache string key
|
||||
// cache string is of the form entryKey:allKeysToFind
|
||||
func makeCacheKey(entry string, keysFound map[string]bool, allKeysNeeded map[string]bool) string {
|
||||
cacheKey := entry
|
||||
// generate sorted list of the keys that have NOT been found yet
|
||||
var needToFind []string
|
||||
for key := range allKeysNeeded {
|
||||
if !keysFound[key] {
|
||||
needToFind = append(needToFind, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(needToFind)
|
||||
|
||||
return cacheKey + strings.Join(needToFind, "")
|
||||
}
|
||||
|
||||
// simple helper function to make sure that all keysNeeded are in keysFound
|
||||
func haveNeededKeys(keysFound, keysNeeded map[string]bool) bool {
|
||||
for key := range keysNeeded {
|
||||
if !keysFound[key] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
INPUT SANITIZATION FUNCTIONS
|
||||
*****************************************************************************************/
|
||||
|
||||
/* Quadrant numbers :) mathematical because why not
|
||||
II | I
|
||||
|
|
||||
--------+---------
|
||||
|
|
||||
III | IV
|
||||
| */
|
||||
// This function is gross but it does what it says it does... returns four quadrants
|
||||
func generateQuadrants(linesSli []string) ([][]string, [][]string, [][]string, [][]string) {
|
||||
quad1 := make([][]string, len(linesSli))
|
||||
quad2 := make([][]string, len(linesSli))
|
||||
quad3 := make([][]string, len(linesSli))
|
||||
quad4 := make([][]string, len(linesSli))
|
||||
for i, line := range linesSli {
|
||||
quad1[i] = strings.Split(line, "")
|
||||
quad2[i] = strings.Split(line, "")
|
||||
quad3[i] = strings.Split(line, "")
|
||||
quad4[i] = strings.Split(line, "")
|
||||
}
|
||||
|
||||
// really only need the "@" coordinates, but might as well use this function that I alreade have..
|
||||
keyToCoordinates := getCoordinatesOfKeys(quad1)
|
||||
|
||||
originRow, originCol := keyToCoordinates["@"][0], keyToCoordinates["@"][1]
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 3; j++ {
|
||||
row := originRow - 1 + i
|
||||
col := originCol - 1 + j
|
||||
if (row+col)%2 == 0 {
|
||||
quad1[row][col] = "@"
|
||||
quad2[row][col] = "@"
|
||||
quad3[row][col] = "@"
|
||||
quad4[row][col] = "@"
|
||||
} else {
|
||||
quad1[row][col] = "#"
|
||||
quad2[row][col] = "#"
|
||||
quad3[row][col] = "#"
|
||||
quad4[row][col] = "#"
|
||||
}
|
||||
}
|
||||
}
|
||||
// replace origin with wall
|
||||
quad1[originRow][originCol] = "#"
|
||||
quad2[originRow][originCol] = "#"
|
||||
quad3[originRow][originCol] = "#"
|
||||
quad4[originRow][originCol] = "#"
|
||||
|
||||
// rescope quadrants to point to correct scope of underlying arrays
|
||||
quad1 = quad1[:originRow+1]
|
||||
quad2 = quad2[:originRow+1]
|
||||
for i := range quad1 {
|
||||
quad1[i] = quad1[i][originCol:]
|
||||
quad2[i] = quad2[i][:originCol+1]
|
||||
}
|
||||
quad3 = quad3[originRow:]
|
||||
quad4 = quad4[originRow:]
|
||||
for i := range quad3 {
|
||||
quad3[i] = quad3[i][:originCol+1]
|
||||
quad4[i] = quad4[i][originCol:]
|
||||
}
|
||||
|
||||
// return the four quads
|
||||
return quad1, quad2, quad3, quad4
|
||||
}
|
||||
|
||||
// NOTE this is a BIG assumption that will not work on all inputs, in fact it fails on a lot of the
|
||||
// note examples... :(
|
||||
// removes doors from a quadrant if the associated key is not in the quadrant
|
||||
func removeDoorsWithoutKeys(quadrant [][]string) {
|
||||
// put all the keys in this quadrant in a hashmap (along with walls, floors and the origin "@")
|
||||
valuesToKeep := map[string]bool{"#": true, ".": true, "@": true}
|
||||
for _, rowSli := range quadrant {
|
||||
for _, cell := range rowSli {
|
||||
if ascii := int(cell[0] - 'a'); ascii >= 0 && ascii < 26 {
|
||||
valuesToKeep[string(ascii+'a')] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iterate through the quadrant again, this time removing any capital letters
|
||||
// who's lower case representation is NOT in the valuesToKeep hashmap
|
||||
for row, rowSli := range quadrant {
|
||||
for col, cell := range rowSli {
|
||||
if lower := strings.ToLower(cell); !valuesToKeep[lower] {
|
||||
quadrant[row][col] = "." // replace with an empty hallway
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
--- Day 18: Many-Worlds Interpretation ---
|
||||
As you approach Neptune, a planetary security system detects you and activates a giant tractor beam on Triton! You have no choice but to land.
|
||||
|
||||
A scan of the local area reveals only one interesting feature: a massive underground vault. You generate a map of the tunnels (your puzzle input). The tunnels are too narrow to move diagonally.
|
||||
|
||||
Only one entrance (marked @) is present among the open passages (marked .) and stone walls (#), but you also detect an assortment of keys (shown as lowercase letters) and doors (shown as uppercase letters). Keys of a given letter open the door of the same letter: a opens A, b opens B, and so on. You aren't sure which key you need to disable the tractor beam, so you'll need to collect all of them.
|
||||
|
||||
For example, suppose you have the following map:
|
||||
|
||||
#########
|
||||
#b.A.@.a#
|
||||
#########
|
||||
Starting from the entrance (@), you can only access a large door (A) and a key (a). Moving toward the door doesn't help you, but you can move 2 steps to collect the key, unlocking A in the process:
|
||||
|
||||
#########
|
||||
#b.....@#
|
||||
#########
|
||||
Then, you can move 6 steps to collect the only other key, b:
|
||||
|
||||
#########
|
||||
#@......#
|
||||
#########
|
||||
So, collecting every key took a total of 8 steps.
|
||||
|
||||
Here is a larger example:
|
||||
|
||||
########################
|
||||
#f.D.E.e.C.b.A.@.a.B.c.#
|
||||
######################.#
|
||||
#d.....................#
|
||||
########################
|
||||
The only reasonable move is to take key a and unlock door A:
|
||||
|
||||
########################
|
||||
#f.D.E.e.C.b.....@.B.c.#
|
||||
######################.#
|
||||
#d.....................#
|
||||
########################
|
||||
Then, do the same with key b:
|
||||
|
||||
########################
|
||||
#f.D.E.e.C.@.........c.#
|
||||
######################.#
|
||||
#d.....................#
|
||||
########################
|
||||
...and the same with key c:
|
||||
|
||||
########################
|
||||
#f.D.E.e.............@.#
|
||||
######################.#
|
||||
#d.....................#
|
||||
########################
|
||||
Now, you have a choice between keys d and e. While key e is closer, collecting it now would be slower in the long run than collecting key d first, so that's the best choice:
|
||||
|
||||
########################
|
||||
#f...E.e...............#
|
||||
######################.#
|
||||
#@.....................#
|
||||
########################
|
||||
Finally, collect key e to unlock door E, then collect key f, taking a grand total of 86 steps.
|
||||
|
||||
Here are a few more examples:
|
||||
|
||||
########################
|
||||
#...............b.C.D.f#
|
||||
#.######################
|
||||
#.....@.a.B.c.d.A.e.F.g#
|
||||
########################
|
||||
Shortest path is 132 steps: b, a, c, d, f, e, g
|
||||
|
||||
#################
|
||||
#i.G..c...e..H.p#
|
||||
########.########
|
||||
#j.A..b...f..D.o#
|
||||
########@########
|
||||
#k.E..a...g..B.n#
|
||||
########.########
|
||||
#l.F..d...h..C.m#
|
||||
#################
|
||||
Shortest paths are 136 steps;
|
||||
one is: a, f, b, j, g, n, h, d, l, o, e, p, c, i, k, m
|
||||
|
||||
########################
|
||||
#@..............ac.GI.b#
|
||||
###d#e#f################
|
||||
###A#B#C################
|
||||
###g#h#i################
|
||||
########################
|
||||
Shortest paths are 81 steps; one is: a, c, f, i, d, g, b, e, h
|
||||
|
||||
How many steps is the shortest path that collects all of the keys?
|
||||
|
||||
Your puzzle answer was 6286.
|
||||
|
||||
The first half of this puzzle is complete! It provides one gold star: *
|
||||
|
||||
--- Part Two ---
|
||||
You arrive at the vault only to discover that there is not one vault, but four - each with its own entrance.
|
||||
|
||||
On your map, find the area in the middle that looks like this:
|
||||
|
||||
...
|
||||
.@.
|
||||
...
|
||||
Update your map to instead use the correct data:
|
||||
|
||||
@#@
|
||||
###
|
||||
@#@
|
||||
This change will split your map into four separate sections, each with its own entrance:
|
||||
|
||||
####### #######
|
||||
#a.#Cd# #a.#Cd#
|
||||
##...## ##@#@##
|
||||
##.@.## --> #######
|
||||
##...## ##@#@##
|
||||
#cB#Ab# #cB#Ab#
|
||||
####### #######
|
||||
Because some of the keys are for doors in other vaults, it would take much too long to collect all of the keys by yourself. Instead, you deploy four remote-controlled robots. Each starts at one of the entrances (@).
|
||||
|
||||
Your goal is still to collect all of the keys in the fewest steps, but now, each robot has its own position and can move independently. You can only remotely control a single robot at a time. Collecting a key instantly unlocks any corresponding doors, regardless of the vault in which the key or door is found.
|
||||
|
||||
For example, in the map above, the top-left robot first collects key a, unlocking door A in the bottom-right vault:
|
||||
|
||||
#######
|
||||
#@.#Cd#
|
||||
##.#@##
|
||||
#######
|
||||
##@#@##
|
||||
#cB#.b#
|
||||
#######
|
||||
Then, the bottom-right robot collects key b, unlocking door B in the bottom-left vault:
|
||||
|
||||
#######
|
||||
#@.#Cd#
|
||||
##.#@##
|
||||
#######
|
||||
##@#.##
|
||||
#c.#.@#
|
||||
#######
|
||||
Then, the bottom-left robot collects key c:
|
||||
|
||||
#######
|
||||
#@.#.d#
|
||||
##.#@##
|
||||
#######
|
||||
##.#.##
|
||||
#@.#.@#
|
||||
#######
|
||||
Finally, the top-right robot collects key d:
|
||||
|
||||
#######
|
||||
#@.#.@#
|
||||
##.#.##
|
||||
#######
|
||||
##.#.##
|
||||
#@.#.@#
|
||||
#######
|
||||
In this example, it only took 8 steps to collect all of the keys.
|
||||
|
||||
Sometimes, multiple robots might have keys available, or a robot might have to wait for multiple keys to be collected:
|
||||
|
||||
###############
|
||||
#d.ABC.#.....a#
|
||||
######@#@######
|
||||
###############
|
||||
######@#@######
|
||||
#b.....#.....c#
|
||||
###############
|
||||
First, the top-right, bottom-left, and bottom-right robots take turns collecting keys a, b, and c, a total of 6 + 6 + 6 = 18 steps. Then, the top-left robot can access key d, spending another 6 steps; collecting all of the keys here takes a minimum of 24 steps.
|
||||
|
||||
Here's a more complex example:
|
||||
|
||||
#############
|
||||
#DcBa.#.GhKl#
|
||||
#.###@#@#I###
|
||||
#e#d#####j#k#
|
||||
###C#@#@###J#
|
||||
#fEbA.#.FgHi#
|
||||
#############
|
||||
Top-left robot collects key a.
|
||||
Bottom-left robot collects key b.
|
||||
Top-left robot collects key c.
|
||||
Bottom-left robot collects key d.
|
||||
Top-left robot collects key e.
|
||||
Bottom-left robot collects key f.
|
||||
Bottom-right robot collects key g.
|
||||
Top-right robot collects key h.
|
||||
Bottom-right robot collects key i.
|
||||
Top-right robot collects key j.
|
||||
Bottom-right robot collects key k.
|
||||
Top-right robot collects key l.
|
||||
In the above example, the fewest steps to collect all of the keys is 32.
|
||||
|
||||
Here's an example with more choices:
|
||||
|
||||
#############
|
||||
#g#f.D#..h#l#
|
||||
#F###e#E###.#
|
||||
#dCba@#@BcIJ#
|
||||
#############
|
||||
#nK.L@#@G...#
|
||||
#M###N#H###.#
|
||||
#o#m..#i#jk.#
|
||||
#############
|
||||
One solution with the fewest steps is:
|
||||
|
||||
Top-left robot collects key e.
|
||||
Top-right robot collects key h.
|
||||
Bottom-right robot collects key i.
|
||||
Top-left robot collects key a.
|
||||
Top-left robot collects key b.
|
||||
Top-right robot collects key c.
|
||||
Top-left robot collects key d.
|
||||
Top-left robot collects key f.
|
||||
Top-left robot collects key g.
|
||||
Bottom-right robot collects key k.
|
||||
Bottom-right robot collects key j.
|
||||
Top-right robot collects key l.
|
||||
Bottom-left robot collects key n.
|
||||
Bottom-left robot collects key m.
|
||||
Bottom-left robot collects key o.
|
||||
This example requires at least 72 steps to collect all keys.
|
||||
|
||||
After updating your map and using the remote-controlled robots, what is the fewest steps necessary to collect all of the keys?
|
||||
Reference in New Issue
Block a user