mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
added day15 solutions
This commit is contained in:
@@ -20,4 +20,4 @@ Day | Name | Type of Algo & Notes
|
||||
12 | The N-Body Problem | I like to call this a _(harmonic) frequency_ algo. Finding the harmonic frequency of multiple bodies/items and then finding the Least Common Multiple of those frequencies will tell you when all the bodies have returned to their initial state. <br> - I've used this approach for a leetcode problem about prisoners in jail cells too
|
||||
13 | Care Package | Intcode again! It's basically every other day... <br> - part1: 2D array manipulation again <br> - part2: holy algo, some logic to basically play Bricks game. <br> - This is more of a design question for how you manage state
|
||||
14 | Space Stoichiometry | __Weighted Graph and Breadth First Traversals__ <br> - Because not all of the products have a quantity of 1, it complicates the graph's data model. I ended up mapping the product/chemical name to a map of its stoichiometry where the product's number is positive & reactants were negative. <br> - part2: not just a simple division because of "extra byproducts" of generating each piece of fuel. I just let this brute force thing run for ~30 seconds...
|
||||
15 | Oxygen System | YAY INTCODE 🙄 <br>
|
||||
15 | Oxygen System | YAY INTCODE 🙄 <br> - Combination of __searching algo__, __backtracking algo__ and the the Intcode... <br> - I've realized that I really need to stop using x and y for 2D grids and start using row and col because mathematically x is horizontal and y is vertical... My brain is all jumbled up <br> - Created a Robot struct/class that has a computer inside of it. It goes and searches around, collecting data on the floor types at various coordinates. That data is transformed into a 2D grid/array, and then finally fed into a backtracking, searching algorithm to determine the shortest path (turns out there's only one path to the O2 tank...) <br> - part2 is fairly straight forward 2D grid traversing and tagging a spread of oxygen to valid tiles/hallway spaces
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3,1033,1008,1033,1,1032,1005,1032,31,1008,1033,2,1032,1005,1032,58,1008,1033,3,1032,1005,1032,81,1008,1033,4,1032,1005,1032,104,99,1002,1034,1,1039,1001,1036,0,1041,1001,1035,-1,1040,1008,1038,0,1043,102,-1,1043,1032,1,1037,1032,1042,1105,1,124,1001,1034,0,1039,102,1,1036,1041,1001,1035,1,1040,1008,1038,0,1043,1,1037,1038,1042,1105,1,124,1001,1034,-1,1039,1008,1036,0,1041,101,0,1035,1040,102,1,1038,1043,1001,1037,0,1042,1106,0,124,1001,1034,1,1039,1008,1036,0,1041,1001,1035,0,1040,102,1,1038,1043,1001,1037,0,1042,1006,1039,217,1006,1040,217,1008,1039,40,1032,1005,1032,217,1008,1040,40,1032,1005,1032,217,1008,1039,9,1032,1006,1032,165,1008,1040,5,1032,1006,1032,165,1101,0,2,1044,1105,1,224,2,1041,1043,1032,1006,1032,179,1102,1,1,1044,1106,0,224,1,1041,1043,1032,1006,1032,217,1,1042,1043,1032,1001,1032,-1,1032,1002,1032,39,1032,1,1032,1039,1032,101,-1,1032,1032,101,252,1032,211,1007,0,40,1044,1106,0,224,1101,0,0,1044,1106,0,224,1006,1044,247,102,1,1039,1034,101,0,1040,1035,101,0,1041,1036,1001,1043,0,1038,1001,1042,0,1037,4,1044,1106,0,0,26,29,83,66,1,36,14,44,33,12,3,15,20,56,9,35,51,55,6,20,13,71,15,23,94,38,45,15,47,30,89,39,11,55,5,9,47,29,41,36,78,12,4,65,48,66,36,94,76,30,63,41,32,1,73,1,35,65,87,46,18,90,11,44,30,73,87,8,38,46,17,78,51,34,19,53,37,26,20,24,46,64,17,6,26,41,10,62,14,88,23,94,13,55,5,45,10,39,83,99,32,34,72,30,58,33,71,47,21,38,97,38,46,41,18,39,37,8,86,55,35,4,92,19,21,53,61,6,55,69,16,85,62,26,63,17,80,33,10,53,91,2,37,94,37,93,7,97,18,55,54,36,17,62,89,12,92,32,69,4,46,47,19,89,25,12,51,91,9,1,71,35,56,39,98,48,7,49,24,95,15,45,2,1,93,82,19,7,11,70,30,64,28,27,58,4,39,30,94,72,33,43,90,98,26,32,70,1,81,25,35,47,17,31,92,15,73,13,27,72,65,30,67,2,22,89,77,30,47,12,58,26,79,22,37,74,41,3,42,30,39,67,24,18,62,98,19,59,95,25,6,67,42,35,85,51,48,7,63,17,67,53,45,13,25,43,1,54,4,65,55,20,73,32,70,1,33,39,93,88,19,35,56,21,13,53,73,31,21,44,73,31,13,69,30,42,26,51,25,90,16,49,9,93,50,28,60,24,18,61,23,11,98,19,45,77,12,61,31,3,66,56,4,77,24,59,87,31,38,65,67,7,9,23,71,9,59,35,55,83,22,12,94,17,67,87,96,63,8,29,32,34,15,55,39,60,41,74,39,81,47,51,25,26,57,28,18,60,84,20,16,66,42,14,25,16,94,2,22,74,85,19,63,32,9,19,11,91,44,34,21,1,56,12,87,8,52,18,56,7,90,5,86,81,24,98,21,9,80,59,68,10,80,53,18,75,50,9,14,43,26,29,57,86,39,41,93,3,69,55,16,84,15,22,84,30,72,19,13,15,19,80,97,79,32,68,77,82,30,19,4,71,45,67,14,95,17,54,80,88,25,13,80,41,37,96,15,28,26,33,73,32,45,79,21,52,23,98,82,21,16,13,64,32,39,93,17,33,95,61,36,12,21,3,84,4,88,22,26,59,80,27,82,2,85,79,29,33,52,17,23,95,8,64,16,56,23,42,43,18,41,11,9,84,42,62,4,67,17,98,76,99,1,16,72,72,10,79,19,76,4,54,9,99,34,33,7,97,85,19,76,93,38,6,90,37,90,2,83,61,19,43,39,2,91,17,60,21,79,2,32,94,38,32,7,64,8,14,7,68,23,28,75,24,73,50,29,63,22,89,4,51,66,2,7,33,82,13,23,84,81,23,55,68,15,27,9,97,27,79,42,86,75,56,13,95,74,5,88,25,44,99,33,14,24,29,21,78,4,15,75,32,92,74,11,56,24,57,10,28,73,8,10,90,77,30,96,8,60,3,71,20,41,9,33,89,38,74,95,4,95,35,13,18,55,10,81,9,60,17,67,7,34,48,48,15,54,79,37,66,43,22,64,28,28,4,91,5,9,92,30,64,37,98,66,15,92,2,3,25,70,25,33,61,56,25,70,58,30,41,97,18,54,10,49,45,3,1,30,57,30,46,8,55,79,39,58,46,35,19,38,80,86,4,36,75,29,62,39,71,2,41,6,66,36,99,21,61,39,72,3,48,29,43,31,59,84,71,12,52,61,82,11,56,23,51,30,60,88,65,35,48,24,58,76,49,93,51,33,72,0,0,21,21,1,10,1,0,0,0,0,0,0
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
Intcode struct is defined within this file
|
||||
Robot struct houses an Intcode computer and its RecursiveMove method populates a map of
|
||||
coordinates to the floor type (-1: wall, 1: hallway, 2: O2 tank, 5: origin)
|
||||
That map is converted into a 2D grid (slice)
|
||||
2D slice is passed to a backtracking & searching algorithm to find the shortest path
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"adventofcode/util"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// read the input file, modify it to a slice of numbers
|
||||
inputFile := util.ReadFile("../input.txt")
|
||||
|
||||
splitStrings := strings.Split(inputFile, ",")
|
||||
|
||||
inputNumbers := make([]int, len(splitStrings))
|
||||
for i, v := range splitStrings {
|
||||
inputNumbers[i], _ = strconv.Atoi(v)
|
||||
}
|
||||
|
||||
robot := MakeRobot(inputNumbers)
|
||||
|
||||
// fire off recursive move function to populate the robot's floorDetails property
|
||||
robot.RecursiveMove()
|
||||
|
||||
// make grid from the map of coordinates to floor types
|
||||
grid := Draw(robot.floorDetails)
|
||||
|
||||
// find the origin coordinates to know where to start recursive searcher from, it was tagged with a 5
|
||||
var originX, originY int
|
||||
for y, row := range grid {
|
||||
for x, tileType := range row {
|
||||
if tileType == 5 {
|
||||
originX = x
|
||||
originY = y
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shortestLength := findShortestLength(grid, originX, originY)
|
||||
fmt.Println("Shortest length is", shortestLength)
|
||||
}
|
||||
|
||||
// Robot struct to maintain detail's on the Robot's coordinates, path
|
||||
type Robot struct {
|
||||
fromTop, fromLeft int
|
||||
floorDetails map[string]int // maps coordinates and type of tile (0 == wall, 1 == path, 2 == oxygen)
|
||||
computer *Intcode
|
||||
}
|
||||
|
||||
// MakeRobot returns an instance of a Robot
|
||||
func MakeRobot(intcodeInput []int) *Robot {
|
||||
return &Robot{
|
||||
0,
|
||||
0,
|
||||
map[string]int{"0,0": 5}, // mark the origin specially with a 5
|
||||
MakeComputer(intcodeInput),
|
||||
}
|
||||
}
|
||||
|
||||
var backtrack map[int]int = map[int]int{
|
||||
1: 2, // north (1), south (2)
|
||||
2: 1,
|
||||
3: 4, // west (3), east(4)
|
||||
4: 3,
|
||||
}
|
||||
|
||||
// dx is the difference to add when traveling in the given direction
|
||||
// i.e. add 0 for north and south, for west decrement 1, for east add 1
|
||||
var dx map[int]int = map[int]int{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: -1,
|
||||
4: 1,
|
||||
}
|
||||
|
||||
// dy is the vertical distance traveled
|
||||
var dy map[int]int = map[int]int{
|
||||
1: 1,
|
||||
2: -1,
|
||||
3: 0,
|
||||
4: 0,
|
||||
}
|
||||
|
||||
// RecursiveMove will populate a robot's floor details property by traveling in all directions
|
||||
// and
|
||||
func (robot *Robot) RecursiveMove() {
|
||||
for i := 1; i <= 4; i++ {
|
||||
// if next coordinates have already been detailed, skip all calculations
|
||||
nextCoords := fmt.Sprintf("%v,%v", robot.fromTop+dy[i], robot.fromLeft+dx[i])
|
||||
|
||||
if robot.floorDetails[nextCoords] == 0 {
|
||||
robot.computer.Step(i)
|
||||
computerOutput := robot.computer.Outputs[len(robot.computer.Outputs)-1]
|
||||
|
||||
switch computerOutput {
|
||||
case 0: // hit a wall, do not recurse
|
||||
// update robot's wall coords to include the wall
|
||||
// note representing walls with a -1 to avoid the zero value detection
|
||||
robot.floorDetails[nextCoords] = -1
|
||||
case 1, 2: // walked and hit the O2 tank or not
|
||||
// update floorDetails
|
||||
robot.floorDetails[nextCoords] = computerOutput
|
||||
|
||||
// continue to walk the robot. walk the robot into the nextCoords spot
|
||||
robot.fromLeft += dx[i]
|
||||
robot.fromTop += dy[i]
|
||||
|
||||
// recurse
|
||||
robot.RecursiveMove()
|
||||
|
||||
// backtrack so the robot walks in the remainder of directions from this output
|
||||
robot.fromLeft -= dx[i]
|
||||
robot.fromTop -= dy[i]
|
||||
// backtrack the computer
|
||||
robot.computer.Step(backtrack[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Intcode is an OOP approach *************************************************
|
||||
MakeComputer is equivalent to the constructor
|
||||
Step takes in an input int and updates properties in the computer:
|
||||
- InstructionIndex: where to read the next instruction from
|
||||
- LastOutput, what the last opcode 4 outputted
|
||||
- PuzzleIndex based if the last instruction modified the puzzle at all
|
||||
****************************************************************************/
|
||||
type Intcode struct {
|
||||
PuzzleInput []int // file/puzzle input parsed into slice of ints
|
||||
InstructionIndex int // stores the index where the next instruction is
|
||||
RelativeBase int // relative base for opcode 9 and param mode 2
|
||||
Outputs []int // stores all outputs
|
||||
IsRunning bool // will be true until a 99 opcode is hit
|
||||
}
|
||||
|
||||
// MakeComputer initializes a new comp
|
||||
func MakeComputer(PuzzleInput []int) *Intcode {
|
||||
puzzleInputCopy := make([]int, len(PuzzleInput))
|
||||
copy(puzzleInputCopy, PuzzleInput)
|
||||
|
||||
comp := Intcode{
|
||||
puzzleInputCopy,
|
||||
0,
|
||||
0,
|
||||
make([]int, 0),
|
||||
true,
|
||||
}
|
||||
return &comp
|
||||
}
|
||||
|
||||
// Step will read the next 4 values in the input `sli` and make updates
|
||||
// according to the opcodes
|
||||
func (comp *Intcode) Step(input int) {
|
||||
// read the instruction, opcode and the indexes where the params point to
|
||||
opcode, paramIndexes := comp.GetOpCodeAndParamIndexes()
|
||||
param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2]
|
||||
|
||||
// ensure params are within the bounds of PuzzleInput, resize if necessary
|
||||
// Note: need to optimize this to not resize if the params are not being used
|
||||
switch opcode {
|
||||
case 1, 2, 7, 8:
|
||||
comp.ResizeMemory(param1, param2, param3)
|
||||
case 5, 6:
|
||||
comp.ResizeMemory(param1, param2)
|
||||
case 3, 4, 9:
|
||||
comp.ResizeMemory(param1)
|
||||
}
|
||||
|
||||
switch opcode {
|
||||
case 99: // 99: Terminates program
|
||||
fmt.Println("Terminating...")
|
||||
comp.IsRunning = false
|
||||
case 1: // 1: Add next two paramIndexes, store in third
|
||||
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2]
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
case 2: // 2: Multiply next two and store in third
|
||||
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2]
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
case 3: // 3: Takes one input and saves it to position of one parameter
|
||||
// check if input has already been used (i.e. input == -1)
|
||||
// if it's been used, return out to prevent further Steps
|
||||
// NOTE: making a big assumption that -1 will never be an input...
|
||||
if input == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
// else recurse with a -1 to signal the initial input has been processed
|
||||
comp.PuzzleInput[param1] = input
|
||||
comp.InstructionIndex += 2
|
||||
comp.Step(-1)
|
||||
case 4: // 4: outputs its input value
|
||||
// set LastOutput of the computer & log it
|
||||
comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1])
|
||||
// fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput)
|
||||
comp.InstructionIndex += 2
|
||||
|
||||
// continue running until terminates or asks for another input
|
||||
comp.Step(input)
|
||||
// 5: jump-if-true: if first param != 0, move pointer to second param, else nothing
|
||||
case 5:
|
||||
if comp.PuzzleInput[param1] != 0 {
|
||||
comp.InstructionIndex = comp.PuzzleInput[param2]
|
||||
} else {
|
||||
comp.InstructionIndex += 3
|
||||
}
|
||||
comp.Step(input)
|
||||
// 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing
|
||||
case 6:
|
||||
if comp.PuzzleInput[param1] == 0 {
|
||||
comp.InstructionIndex = comp.PuzzleInput[param2]
|
||||
} else {
|
||||
comp.InstructionIndex += 3
|
||||
}
|
||||
comp.Step(input)
|
||||
// 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0
|
||||
case 7:
|
||||
if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] {
|
||||
comp.PuzzleInput[param3] = 1
|
||||
} else {
|
||||
comp.PuzzleInput[param3] = 0
|
||||
}
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
// 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0
|
||||
case 8:
|
||||
if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] {
|
||||
comp.PuzzleInput[param3] = 1
|
||||
} else {
|
||||
comp.PuzzleInput[param3] = 0
|
||||
}
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
// 9: adjust relative base
|
||||
case 9:
|
||||
comp.RelativeBase += comp.PuzzleInput[param1]
|
||||
comp.InstructionIndex += 2
|
||||
comp.Step(input)
|
||||
default:
|
||||
log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex])
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex]
|
||||
- opcode will be the left two digits, mod by 100 will get that
|
||||
- rest of instructions will be grabbed via mod 10
|
||||
- these also have to be parsed for the
|
||||
*/
|
||||
func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) {
|
||||
instruction := comp.PuzzleInput[comp.InstructionIndex]
|
||||
|
||||
// opcode is the lowest two digits, so mod by 100
|
||||
opcode := instruction % 100
|
||||
instruction /= 100
|
||||
|
||||
// assign the indexes that need to be read by reading the parameter modes
|
||||
var paramIndexes [3]int
|
||||
for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ {
|
||||
// grab the mode with a mod, last digit
|
||||
mode := instruction % 10
|
||||
instruction /= 10
|
||||
|
||||
switch mode {
|
||||
case 0: // position mode, index will be the value at the index
|
||||
paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i]
|
||||
case 1: // immediate mode, the index itself
|
||||
paramIndexes[i-1] = comp.InstructionIndex + i
|
||||
case 2: // relative mode, like position mode but index is added to relative base
|
||||
paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase
|
||||
}
|
||||
}
|
||||
|
||||
return opcode, paramIndexes
|
||||
}
|
||||
|
||||
// ResizeMemory will take any number of integers and resize the computer's memory appropriately
|
||||
func (comp *Intcode) ResizeMemory(sizes ...int) {
|
||||
// get largest of input sizes
|
||||
maxArg := sizes[0]
|
||||
for _, v := range sizes {
|
||||
if v > maxArg {
|
||||
maxArg = v
|
||||
}
|
||||
}
|
||||
|
||||
// resize if PuzzleInput's length is shorter
|
||||
if maxArg >= len(comp.PuzzleInput) {
|
||||
// make empty slice to copy into, of the new, larger size
|
||||
resizedPuzzleInput := make([]int, maxArg+1)
|
||||
// copy old puzzle input values in
|
||||
copy(resizedPuzzleInput, comp.PuzzleInput)
|
||||
|
||||
// overwrite puzzle input
|
||||
comp.PuzzleInput = resizedPuzzleInput
|
||||
}
|
||||
}
|
||||
|
||||
// Draw was copied from day11. It converts a map of points mapped from a (0,0) origin to a 2D grid
|
||||
// The origin loses its reference...
|
||||
func Draw(mapCoordsToType map[string]int) [][]int {
|
||||
var lowX, highX, lowY, highY int
|
||||
for key := range mapCoordsToType {
|
||||
coords := strings.Split(key, ",")
|
||||
x, _ := strconv.Atoi(coords[0])
|
||||
y, _ := strconv.Atoi(coords[1])
|
||||
switch {
|
||||
case x < lowX:
|
||||
lowX = x
|
||||
case x > highX:
|
||||
highX = x
|
||||
}
|
||||
switch {
|
||||
case y < lowY:
|
||||
lowY = y
|
||||
case y > highY:
|
||||
highY = y
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the bounds of the grid
|
||||
edgeLength := 2 * util.MaxInts(-lowY, -lowX, highY, highX)
|
||||
|
||||
grid := make([][]int, edgeLength)
|
||||
for i := 0; i < edgeLength; i++ {
|
||||
// each character will initialize as a space character
|
||||
grid[i] = make([]int, edgeLength)
|
||||
}
|
||||
|
||||
// Iterate through all coordinates and transcribe x,y onto a 2D grid
|
||||
// where the math is a little different...
|
||||
for key, val := range mapCoordsToType {
|
||||
// key is string coords
|
||||
coords := strings.Split(key, ",")
|
||||
x, _ := strconv.Atoi(coords[0])
|
||||
y, _ := strconv.Atoi(coords[1])
|
||||
x += edgeLength / 2
|
||||
y += edgeLength / 2
|
||||
// val is color to paint (1 or 0)
|
||||
if val != -1 {
|
||||
grid[x][y] = val
|
||||
}
|
||||
}
|
||||
|
||||
// trim off due to making the initial grid too large
|
||||
grid = trim(grid)
|
||||
// rotate it because of how I coded up the robot's coordinates :/
|
||||
grid = util.RotateGridInts(grid)
|
||||
// retrim
|
||||
grid = trim(grid)
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
// helper function for Draw to remove whitespace from overestimating the size
|
||||
// of the drawing space
|
||||
func trim(grid [][]int) [][]int {
|
||||
// remove all empty rows at top and bottom
|
||||
removeRowsTop:
|
||||
for i := 0; i < len(grid); {
|
||||
for j := 0; j < len(grid[i]); j++ {
|
||||
if grid[i][j] != 0 {
|
||||
break removeRowsTop
|
||||
}
|
||||
}
|
||||
grid = grid[1:]
|
||||
}
|
||||
|
||||
// remove empty columns on left
|
||||
removeColsLeft:
|
||||
for i := 0; i < len(grid[0]); {
|
||||
for j := 0; j < len(grid); j++ {
|
||||
if grid[j][i] != 0 {
|
||||
break removeColsLeft
|
||||
}
|
||||
}
|
||||
// if loop hasn't broken out, iterate over first "column" and slice off "0-index"
|
||||
for j := 0; j < len(grid); j++ {
|
||||
grid[j] = grid[j][1:]
|
||||
}
|
||||
}
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
// findShortestLength is a (fairly) standard brute force searching algorithm that will return the shortest path from origin to the O2 tank (cell with a 2)
|
||||
func findShortestLength(grid [][]int, startX, startY int) int {
|
||||
shortestLength := util.MaxSafeInt
|
||||
|
||||
var recurse func(x, y, pathLength int)
|
||||
recurse = func(x, y, pathLength int) {
|
||||
if grid[y][x] == 2 {
|
||||
if pathLength < shortestLength {
|
||||
shortestLength = pathLength
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for i := 1; i <= 4; i++ {
|
||||
nextX, nextY := x+dx[i], y+dy[i]
|
||||
// if it is inbounds and the next coordinate is not a wall or something already traversed
|
||||
inBounds := nextX >= 0 && nextX < len(grid[0]) && nextY >= 0 && nextY < len(grid)
|
||||
if inBounds && grid[nextY][nextX] != 0 {
|
||||
oldVal := grid[y][x]
|
||||
|
||||
// mark as "seen" by making it a "fake" wall
|
||||
grid[y][x] = 0
|
||||
|
||||
// recurse into the next coordinate
|
||||
recurse(nextX, nextY, pathLength+1)
|
||||
|
||||
// undo fake wall
|
||||
grid[y][x] = oldVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurse(startX, startY, 0)
|
||||
return shortestLength
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
Intcode struct is defined within this file
|
||||
Robot struct houses an Intcode computer and its RecursiveMove method populates a map of
|
||||
coordinates to the floor type (-1: wall, 1: hallway, 2: O2 tank, 5: origin)
|
||||
That map is converted into a 2D grid (slice)
|
||||
|
||||
The shortest length is no longer needed
|
||||
|
||||
Two functions added that determine if the space is full of O2 and to spreadOxygen for one minute
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"adventofcode/util"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// read the input file, modify it to a slice of numbers
|
||||
inputFile := util.ReadFile("../input.txt")
|
||||
|
||||
splitStrings := strings.Split(inputFile, ",")
|
||||
|
||||
inputNumbers := make([]int, len(splitStrings))
|
||||
for i, v := range splitStrings {
|
||||
inputNumbers[i], _ = strconv.Atoi(v)
|
||||
}
|
||||
|
||||
robot := MakeRobot(inputNumbers)
|
||||
|
||||
// fire off recursive move function to populate the robot's floorDetails property
|
||||
robot.RecursiveMove()
|
||||
|
||||
// make grid from the map of coordinates to floor types
|
||||
grid := Draw(robot.floorDetails)
|
||||
|
||||
// overwrite the origin with a 1 (open floor space)
|
||||
for y, row := range grid {
|
||||
for x, tileType := range row {
|
||||
if tileType == 5 {
|
||||
grid[y][x] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// while the grid is not full of oxygen, spread oxygen and increment minutes
|
||||
var minutes int
|
||||
for !isFullOfOxygen(grid) {
|
||||
spreadOxygen(grid)
|
||||
minutes++
|
||||
}
|
||||
|
||||
fmt.Println("Minutes elapsed", minutes)
|
||||
}
|
||||
|
||||
func isFullOfOxygen(grid [][]int) bool {
|
||||
for _, row := range grid {
|
||||
for _, tile := range row {
|
||||
// if there is a hallway space that is not filled with O2, return false
|
||||
if tile == 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// if entire looping passes, return true
|
||||
return true
|
||||
}
|
||||
|
||||
var dRow []int = []int{0, 0, -1, 1}
|
||||
var dCol []int = []int{-1, 1, 0, 0}
|
||||
|
||||
// spreadOxygen will spread all oxygen to one neighboring cell
|
||||
// returns boolean true if O2 has not spread everywhere (i.e. run again), false if O2 is everywhere
|
||||
func spreadOxygen(grid [][]int) {
|
||||
// traverse through grid and mark all cells that are a 1 and have a neighboring 2
|
||||
// tag with -1
|
||||
for i := 0; i < len(grid); i++ {
|
||||
for j := 0; j < len(grid[0]); j++ {
|
||||
if grid[i][j] == 1 {
|
||||
// traverse around neighbors
|
||||
for d := 0; d < 4; d++ {
|
||||
neighborRow := i + dRow[d]
|
||||
neighborCol := j + dCol[d]
|
||||
inBounds := neighborRow >= 0 && neighborRow < len(grid) && neighborCol >= 0 && neighborCol < len(grid[0])
|
||||
// if a neighboring cell is a 2 (i.e. filled with oxygen), then mark this cell
|
||||
if inBounds && grid[neighborRow][neighborCol] == 2 {
|
||||
grid[i][j] = -1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then iterate through again changing all -1's to a 2
|
||||
for i := 0; i < len(grid); i++ {
|
||||
for j := 0; j < len(grid); j++ {
|
||||
if grid[i][j] == -1 {
|
||||
grid[i][j] = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Robot struct to maintain detail's on the Robot's coordinates, path
|
||||
type Robot struct {
|
||||
fromTop, fromLeft int
|
||||
floorDetails map[string]int // maps coordinates and type of tile (0 == wall, 1 == path, 2 == oxygen)
|
||||
computer *Intcode
|
||||
}
|
||||
|
||||
// MakeRobot returns an instance of a Robot
|
||||
func MakeRobot(intcodeInput []int) *Robot {
|
||||
return &Robot{
|
||||
0,
|
||||
0,
|
||||
map[string]int{"0,0": 5}, // mark the origin specially with a 5
|
||||
MakeComputer(intcodeInput),
|
||||
}
|
||||
}
|
||||
|
||||
var backtrack map[int]int = map[int]int{
|
||||
1: 2, // north (1), south (2)
|
||||
2: 1,
|
||||
3: 4, // west (3), east(4)
|
||||
4: 3,
|
||||
}
|
||||
|
||||
// dx is the difference to add when traveling in the given direction
|
||||
// i.e. add 0 for north and south, for west decrement 1, for east add 1
|
||||
var dx map[int]int = map[int]int{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: -1,
|
||||
4: 1,
|
||||
}
|
||||
|
||||
// dy is the vertical distance traveled
|
||||
var dy map[int]int = map[int]int{
|
||||
1: 1,
|
||||
2: -1,
|
||||
3: 0,
|
||||
4: 0,
|
||||
}
|
||||
|
||||
// RecursiveMove will populate a robot's floor details property by traveling in all directions
|
||||
// and
|
||||
func (robot *Robot) RecursiveMove() {
|
||||
for i := 1; i <= 4; i++ {
|
||||
// if next coordinates have already been detailed, skip all calculations
|
||||
nextCoords := fmt.Sprintf("%v,%v", robot.fromTop+dy[i], robot.fromLeft+dx[i])
|
||||
|
||||
if robot.floorDetails[nextCoords] == 0 {
|
||||
robot.computer.Step(i)
|
||||
computerOutput := robot.computer.Outputs[len(robot.computer.Outputs)-1]
|
||||
|
||||
switch computerOutput {
|
||||
case 0: // hit a wall, do not recurse
|
||||
// update robot's wall coords to include the wall
|
||||
// note representing walls with a -1 to avoid the zero value detection
|
||||
robot.floorDetails[nextCoords] = -1
|
||||
case 1, 2: // walked and hit the O2 tank or not
|
||||
// update floorDetails
|
||||
robot.floorDetails[nextCoords] = computerOutput
|
||||
|
||||
// continue to walk the robot. walk the robot into the nextCoords spot
|
||||
robot.fromLeft += dx[i]
|
||||
robot.fromTop += dy[i]
|
||||
|
||||
// recurse
|
||||
robot.RecursiveMove()
|
||||
|
||||
// backtrack so the robot walks in the remainder of directions from this output
|
||||
robot.fromLeft -= dx[i]
|
||||
robot.fromTop -= dy[i]
|
||||
// backtrack the computer
|
||||
robot.computer.Step(backtrack[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Intcode is an OOP approach *************************************************
|
||||
MakeComputer is equivalent to the constructor
|
||||
Step takes in an input int and updates properties in the computer:
|
||||
- InstructionIndex: where to read the next instruction from
|
||||
- LastOutput, what the last opcode 4 outputted
|
||||
- PuzzleIndex based if the last instruction modified the puzzle at all
|
||||
****************************************************************************/
|
||||
type Intcode struct {
|
||||
PuzzleInput []int // file/puzzle input parsed into slice of ints
|
||||
InstructionIndex int // stores the index where the next instruction is
|
||||
RelativeBase int // relative base for opcode 9 and param mode 2
|
||||
Outputs []int // stores all outputs
|
||||
IsRunning bool // will be true until a 99 opcode is hit
|
||||
}
|
||||
|
||||
// MakeComputer initializes a new comp
|
||||
func MakeComputer(PuzzleInput []int) *Intcode {
|
||||
puzzleInputCopy := make([]int, len(PuzzleInput))
|
||||
copy(puzzleInputCopy, PuzzleInput)
|
||||
|
||||
comp := Intcode{
|
||||
puzzleInputCopy,
|
||||
0,
|
||||
0,
|
||||
make([]int, 0),
|
||||
true,
|
||||
}
|
||||
return &comp
|
||||
}
|
||||
|
||||
// Step will read the next 4 values in the input `sli` and make updates
|
||||
// according to the opcodes
|
||||
func (comp *Intcode) Step(input int) {
|
||||
// read the instruction, opcode and the indexes where the params point to
|
||||
opcode, paramIndexes := comp.GetOpCodeAndParamIndexes()
|
||||
param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2]
|
||||
|
||||
// ensure params are within the bounds of PuzzleInput, resize if necessary
|
||||
// Note: need to optimize this to not resize if the params are not being used
|
||||
switch opcode {
|
||||
case 1, 2, 7, 8:
|
||||
comp.ResizeMemory(param1, param2, param3)
|
||||
case 5, 6:
|
||||
comp.ResizeMemory(param1, param2)
|
||||
case 3, 4, 9:
|
||||
comp.ResizeMemory(param1)
|
||||
}
|
||||
|
||||
switch opcode {
|
||||
case 99: // 99: Terminates program
|
||||
fmt.Println("Terminating...")
|
||||
comp.IsRunning = false
|
||||
case 1: // 1: Add next two paramIndexes, store in third
|
||||
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2]
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
case 2: // 2: Multiply next two and store in third
|
||||
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2]
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
case 3: // 3: Takes one input and saves it to position of one parameter
|
||||
// check if input has already been used (i.e. input == -1)
|
||||
// if it's been used, return out to prevent further Steps
|
||||
// NOTE: making a big assumption that -1 will never be an input...
|
||||
if input == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
// else recurse with a -1 to signal the initial input has been processed
|
||||
comp.PuzzleInput[param1] = input
|
||||
comp.InstructionIndex += 2
|
||||
comp.Step(-1)
|
||||
case 4: // 4: outputs its input value
|
||||
// set LastOutput of the computer & log it
|
||||
comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1])
|
||||
// fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput)
|
||||
comp.InstructionIndex += 2
|
||||
|
||||
// continue running until terminates or asks for another input
|
||||
comp.Step(input)
|
||||
// 5: jump-if-true: if first param != 0, move pointer to second param, else nothing
|
||||
case 5:
|
||||
if comp.PuzzleInput[param1] != 0 {
|
||||
comp.InstructionIndex = comp.PuzzleInput[param2]
|
||||
} else {
|
||||
comp.InstructionIndex += 3
|
||||
}
|
||||
comp.Step(input)
|
||||
// 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing
|
||||
case 6:
|
||||
if comp.PuzzleInput[param1] == 0 {
|
||||
comp.InstructionIndex = comp.PuzzleInput[param2]
|
||||
} else {
|
||||
comp.InstructionIndex += 3
|
||||
}
|
||||
comp.Step(input)
|
||||
// 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0
|
||||
case 7:
|
||||
if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] {
|
||||
comp.PuzzleInput[param3] = 1
|
||||
} else {
|
||||
comp.PuzzleInput[param3] = 0
|
||||
}
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
// 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0
|
||||
case 8:
|
||||
if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] {
|
||||
comp.PuzzleInput[param3] = 1
|
||||
} else {
|
||||
comp.PuzzleInput[param3] = 0
|
||||
}
|
||||
comp.InstructionIndex += 4
|
||||
comp.Step(input)
|
||||
// 9: adjust relative base
|
||||
case 9:
|
||||
comp.RelativeBase += comp.PuzzleInput[param1]
|
||||
comp.InstructionIndex += 2
|
||||
comp.Step(input)
|
||||
default:
|
||||
log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex])
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex]
|
||||
- opcode will be the left two digits, mod by 100 will get that
|
||||
- rest of instructions will be grabbed via mod 10
|
||||
- these also have to be parsed for the
|
||||
*/
|
||||
func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) {
|
||||
instruction := comp.PuzzleInput[comp.InstructionIndex]
|
||||
|
||||
// opcode is the lowest two digits, so mod by 100
|
||||
opcode := instruction % 100
|
||||
instruction /= 100
|
||||
|
||||
// assign the indexes that need to be read by reading the parameter modes
|
||||
var paramIndexes [3]int
|
||||
for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ {
|
||||
// grab the mode with a mod, last digit
|
||||
mode := instruction % 10
|
||||
instruction /= 10
|
||||
|
||||
switch mode {
|
||||
case 0: // position mode, index will be the value at the index
|
||||
paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i]
|
||||
case 1: // immediate mode, the index itself
|
||||
paramIndexes[i-1] = comp.InstructionIndex + i
|
||||
case 2: // relative mode, like position mode but index is added to relative base
|
||||
paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase
|
||||
}
|
||||
}
|
||||
|
||||
return opcode, paramIndexes
|
||||
}
|
||||
|
||||
// ResizeMemory will take any number of integers and resize the computer's memory appropriately
|
||||
func (comp *Intcode) ResizeMemory(sizes ...int) {
|
||||
// get largest of input sizes
|
||||
maxArg := sizes[0]
|
||||
for _, v := range sizes {
|
||||
if v > maxArg {
|
||||
maxArg = v
|
||||
}
|
||||
}
|
||||
|
||||
// resize if PuzzleInput's length is shorter
|
||||
if maxArg >= len(comp.PuzzleInput) {
|
||||
// make empty slice to copy into, of the new, larger size
|
||||
resizedPuzzleInput := make([]int, maxArg+1)
|
||||
// copy old puzzle input values in
|
||||
copy(resizedPuzzleInput, comp.PuzzleInput)
|
||||
|
||||
// overwrite puzzle input
|
||||
comp.PuzzleInput = resizedPuzzleInput
|
||||
}
|
||||
}
|
||||
|
||||
// Draw was copied from day11. It converts a map of points mapped from a (0,0) origin to a 2D grid
|
||||
// The origin loses its reference...
|
||||
func Draw(mapCoordsToType map[string]int) [][]int {
|
||||
var lowX, highX, lowY, highY int
|
||||
for key := range mapCoordsToType {
|
||||
coords := strings.Split(key, ",")
|
||||
x, _ := strconv.Atoi(coords[0])
|
||||
y, _ := strconv.Atoi(coords[1])
|
||||
switch {
|
||||
case x < lowX:
|
||||
lowX = x
|
||||
case x > highX:
|
||||
highX = x
|
||||
}
|
||||
switch {
|
||||
case y < lowY:
|
||||
lowY = y
|
||||
case y > highY:
|
||||
highY = y
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the bounds of the grid
|
||||
edgeLength := 2 * util.MaxInts(-lowY, -lowX, highY, highX)
|
||||
|
||||
grid := make([][]int, edgeLength)
|
||||
for i := 0; i < edgeLength; i++ {
|
||||
// each character will initialize as a space character
|
||||
grid[i] = make([]int, edgeLength)
|
||||
}
|
||||
|
||||
// Iterate through all coordinates and transcribe x,y onto a 2D grid
|
||||
// where the math is a little different...
|
||||
for key, val := range mapCoordsToType {
|
||||
// key is string coords
|
||||
coords := strings.Split(key, ",")
|
||||
x, _ := strconv.Atoi(coords[0])
|
||||
y, _ := strconv.Atoi(coords[1])
|
||||
x += edgeLength / 2
|
||||
y += edgeLength / 2
|
||||
// val is color to paint (1 or 0)
|
||||
if val != -1 {
|
||||
grid[x][y] = val
|
||||
}
|
||||
}
|
||||
|
||||
// trim off due to making the initial grid too large
|
||||
grid = trim(grid)
|
||||
// rotate it because of how I coded up the robot's coordinates :/
|
||||
grid = util.RotateGridInts(grid)
|
||||
// retrim
|
||||
grid = trim(grid)
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
// helper function for Draw to remove whitespace from overestimating the size
|
||||
// of the drawing space
|
||||
func trim(grid [][]int) [][]int {
|
||||
// remove all empty rows at top and bottom
|
||||
removeRowsTop:
|
||||
for i := 0; i < len(grid); {
|
||||
for j := 0; j < len(grid[i]); j++ {
|
||||
if grid[i][j] != 0 {
|
||||
break removeRowsTop
|
||||
}
|
||||
}
|
||||
grid = grid[1:]
|
||||
}
|
||||
|
||||
// remove empty columns on left
|
||||
removeColsLeft:
|
||||
for i := 0; i < len(grid[0]); {
|
||||
for j := 0; j < len(grid); j++ {
|
||||
if grid[j][i] != 0 {
|
||||
break removeColsLeft
|
||||
}
|
||||
}
|
||||
// if loop hasn't broken out, iterate over first "column" and slice off "0-index"
|
||||
for j := 0; j < len(grid); j++ {
|
||||
grid[j] = grid[j][1:]
|
||||
}
|
||||
}
|
||||
|
||||
return grid
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package util
|
||||
|
||||
const MaxSafeInt int = 1<<31 - 1
|
||||
@@ -16,3 +16,18 @@ func RotateGrid(grid [][]string) [][]string {
|
||||
}
|
||||
return rotated
|
||||
}
|
||||
|
||||
// RotateGridInts will transpose a 2D array of ints
|
||||
func RotateGridInts(grid [][]int) [][]int {
|
||||
rotated := make([][]int, len(grid[0]))
|
||||
for i := range rotated {
|
||||
rotated[i] = make([]int, len(grid))
|
||||
}
|
||||
|
||||
for i := 0; i < len(grid); i++ {
|
||||
for j := 0; j < len(grid[0]); j++ {
|
||||
rotated[len(grid[0])-1-j][i] = grid[i][j]
|
||||
}
|
||||
}
|
||||
return rotated
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user