Files
advent-of-code-go/2018/day22/main.go
T

286 lines
7.1 KiB
Go

package main
import (
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/data-structures/heap"
"github.com/alexchao26/advent-of-code-go/util"
)
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
depth, targetX, targetY := parseInput(input)
regionTypeCalculator := memoRegionTypeCalculator(depth, targetX, targetY)
var ans int
for x := 0; x <= targetX; x++ {
for y := 0; y <= targetY; y++ {
riskLevel := int(regionTypeCalculator(x, y)) % 3
ans += riskLevel
}
}
return ans
}
func parseInput(input string) (int, int, int) {
lines := strings.Split(input, "\n")
var depth, targetX, targetY int
_, err := fmt.Sscanf(lines[0], "depth: %d", &depth)
if err != nil {
panic("parsing depth from input" + err.Error())
}
_, err = fmt.Sscanf(lines[1], "target: %d,%d", &targetX, &targetY)
if err != nil {
panic("parsing targetX and targetY from input" + err.Error())
}
return depth, targetX, targetY
}
func memoErosionLevelCalculator(depth, targetX, targetY int) func(x, y int) int {
// map to memoize results and prevent branch recursion
xyToErosion := map[[2]int]int{}
var closureGetErosionFunc func(x, y int) int
closureGetErosionFunc = func(x, y int) int {
coords := [2]int{x, y}
if e, ok := xyToErosion[coords]; ok {
return e
}
var geologicIndex int
if coords == [2]int{0, 0} || coords == [2]int{targetX, targetY} {
geologicIndex = 0
} else if y == 0 {
geologicIndex = x * 16807
} else if x == 0 {
geologicIndex = y * 48271
} else {
geologicIndex = closureGetErosionFunc(x-1, y) * closureGetErosionFunc(x, y-1)
}
erosionLevel := (geologicIndex + depth) % 20183
xyToErosion[coords] = erosionLevel
return erosionLevel
}
return closureGetErosionFunc
}
func memoRegionTypeCalculator(depth, targetX, targetY int) func(x, y int) regionType {
erosionCalculator := memoErosionLevelCalculator(depth, targetX, targetY)
xyToRegionType := map[[2]int]regionType{}
var closureRegionFunc func(x, y int) regionType
closureRegionFunc = func(x, y int) regionType {
if x < 0 || y < 0 {
return -1
}
coords := [2]int{x, y}
if rt, ok := xyToRegionType[coords]; ok {
return rt
}
erosion := erosionCalculator(x, y)
rt := regionType(erosion % 3)
xyToRegionType[coords] = rt
return rt
}
return closureRegionFunc
}
func part2(input string) int {
depth, targetX, targetY := parseInput(input)
regionCalculator := memoRegionTypeCalculator(depth, targetX, targetY)
heap := heap.NewMinHeap()
firstNode := node{
coords: [2]int{0, 0},
regionType: regionCalculator(0, 0),
equipped: torch,
totalTime: 0,
}
heap.Add(firstNode)
var eqCoordsToMinDist = map[equipmentType]map[[2]int]int{
neither: map[[2]int]int{},
climbingGear: map[[2]int]int{},
torch: map[[2]int]int{},
}
var currentNode node
for !(currentNode.coords[0] == targetX && currentNode.coords[1] == targetY) {
currentNode = step(heap, eqCoordsToMinDist, regionCalculator)
}
if currentNode.regionType != rocky {
panic("target must be rocky")
}
if currentNode.equipped != torch {
finalTime := currentNode.totalTime + 7
return finalTime
}
return currentNode.totalTime
}
type regionType int
type equipmentType int
const (
rocky regionType = iota // climbing gear or torch
wet // neither or climbing gear
narrow // neither or torch
)
const (
neither equipmentType = iota
climbingGear
torch
)
type node struct {
coords [2]int
equipped equipmentType
regionType regionType
totalTime int
}
func (n node) Value() int {
return n.totalTime
}
func step(heap *heap.MinHeap, eqCoordsToMinDist map[equipmentType]map[[2]int]int, regionCalculator func(x, y int) regionType) node {
// remove node from heap, this will be returned at the end
minNodeInterface := heap.Remove()
if minNodeInterface == nil {
panic("Heap is empty, it shouldn't be empty...")
}
minNode, ok := minNodeInterface.(node)
if !ok {
panic("interface conversion error")
}
// if we've already visited these coordinates with this equipment, check
// that the current time is
t, ok := eqCoordsToMinDist[minNode.equipped][minNode.coords]
if ok && t <= minNode.totalTime {
return node{}
}
eqCoordsToMinDist[minNode.equipped][minNode.coords] = minNode.totalTime
// fmt.Println("coords, region, equipped, time", minNode.coords, minNode.regionType, minNode.equipped, minNode.totalTime)
// fmt.Println(" Steps", minNode.steps)
// first check if movement is possible because this requires less time
// add those steps to the heap
nodesToTravelTo := getNextNodes(minNode, regionCalculator)
// fmt.Println("nodes to travel to", nodesToTravelTo)
for _, n := range nodesToTravelTo {
heap.Add(n)
}
// then try to change equipment if possible
newEq := getSwitchableEquipment(minNode)
// find travelable-to nodes with new equipment on & append if applicable
swappedEquipmentNode := node{
coords: minNode.coords,
equipped: newEq,
regionType: minNode.regionType,
totalTime: minNode.totalTime + 7, // swapping takes 7 minutes
}
// in scope of for loop, just to be safe...
nodesToTravelTo = getNextNodes(swappedEquipmentNode, regionCalculator)
for _, n := range nodesToTravelTo {
heap.Add(n)
}
return minNode
}
var directions = [4][2]int{
{-1, 0},
{1, 0},
{0, -1},
{0, 1},
}
// returns a slice of nodes that represent traveling in the four directions from
// the currentNode
// includes adding 1 to the time
func getNextNodes(currentNode node, regionCalculator func(x, y int) regionType) []node {
var nodesToTravelTo []node
for _, d := range directions {
nextX := currentNode.coords[0] + d[0]
nextY := currentNode.coords[1] + d[1]
nextCoord := [2]int{nextX, nextY}
nextRegionType := regionCalculator(nextX, nextY)
// ensure next region is not out of range
if nextRegionType != -1 {
// check if it can be traveled to
if canTravelTo(nextRegionType, currentNode.equipped) {
nodesToTravelTo = append(nodesToTravelTo, node{
coords: nextCoord,
equipped: currentNode.equipped,
regionType: nextRegionType,
totalTime: currentNode.totalTime + 1,
})
}
}
}
return nodesToTravelTo
}
var mapRegionsToToolsMap = map[regionType]map[equipmentType]bool{
rocky: map[equipmentType]bool{
climbingGear: true,
torch: true,
},
wet: map[equipmentType]bool{
climbingGear: true,
neither: true,
},
narrow: map[equipmentType]bool{
torch: true,
neither: true,
},
}
func canTravelTo(nextRegionType regionType, equippedTool equipmentType) bool {
return mapRegionsToToolsMap[nextRegionType][equippedTool]
}
func getSwitchableEquipment(n node) equipmentType {
for i := 0; i < 3; i++ {
eq := equipmentType(i)
if eq != n.equipped && mapRegionsToToolsMap[n.regionType][eq] {
return eq
}
}
panic("must be one piece of equipment to switch to")
}