Day 15 solution

I wanted to use something like a right-hand wall solver, but the fact that you don't know the maze ahead of time and you can't see what something is without trying to move into it made that difficult. This semi-brute-force approach works well enough. I originally stopped as soon as I found the oxygen system and figured out the shortest path, but once I submitted that answer and saw that part 2 wanted the full map explored, I figured I might as well just fill the map all at once.

I think I would have been stuck on part 1 longer if my input set didn't happen to find the goal system fairly easily (or maybe my debug drawing helped me work through it with that input set specifically, I'm not sure) since a different input set required some tweaking to the max-visited threshold in order to find things that my first input set found with a lower setting.

Regardless, I'm pretty excited that I came to Trémaux's algorithm, more or less, on my own. I went to Wikipedia to see if I was on the right track and lo and behold, I had come to a version of it myself.

Part 2 turned out easier than I originally thought. I suspected this solution would work, but wasn't completely confident. It can only work for the type of maze used by this problem (where there are no loops of open areas). I'm just glad I didn't need A* or anything.

Oh, and this `stringer` command that allows debug printing of enums can be installed with `go install golang.org/x/tools/cmd/stringer@latest`
This commit is contained in:
2022-06-29 08:11:33 -05:00
parent 4cde56eb84
commit f0be3f9f98
7 changed files with 414 additions and 7 deletions

View File

@ -28,7 +28,7 @@ func (d *Day07) Part1() string {
var highestVal int64 var highestVal int64
var highestSequence []int64 var highestSequence []int64
allSequences := utilities.GetPermutations([]int64{0, 1, 2, 3, 4}) allSequences := utilities.GetPermutations([]int64{0, 1, 2, 3, 4}...)
for _, sequence := range allSequences { for _, sequence := range allSequences {
if len(sequence) != len(d.amps) { if len(sequence) != len(d.amps) {
panic("input sequence does not match up to number of amplifiers") panic("input sequence does not match up to number of amplifiers")
@ -68,7 +68,7 @@ func (d *Day07) Part2() string {
var highestVal int64 var highestVal int64
var highestSequence []int64 var highestSequence []int64
allSequences := utilities.GetPermutations([]int64{5, 6, 7, 8, 9}) allSequences := utilities.GetPermutations([]int64{5, 6, 7, 8, 9}...)
for _, sequence := range allSequences { for _, sequence := range allSequences {
if len(sequence) != len(d.amps) { if len(sequence) != len(d.amps) {
panic("input sequence does not match up to number of amplifiers") panic("input sequence does not match up to number of amplifiers")

332
days/15.go Normal file
View File

@ -0,0 +1,332 @@
package days
//go:generate stringer -type=cellStatus,responseType,dirType -output=15_types_string.go
import (
"fmt"
"math"
"sort"
u "parnic.com/aoc2019/utilities"
)
type point u.Pair[int, int]
type cellStatus int
type responseType int
type dirType int
const (
cellStatusUnknown cellStatus = iota
cellStatusWall
cellStatusOpen
cellStatusGoal
)
const (
responseWall responseType = iota
responseSuccess
responseFoundGoal
)
const maxVisited = 3
const (
dirNorth dirType = iota + 1
dirSouth
dirWest
dirEast
dirFirst = dirNorth
dirLast = dirEast
)
var dirOrders = [][]dirType{
{dirNorth, dirSouth, dirWest, dirEast},
{dirSouth, dirWest, dirEast, dirNorth},
{dirWest, dirEast, dirNorth, dirSouth},
{dirEast, dirNorth, dirSouth, dirWest},
}
// turned out to be unnecessary on multiple datasets i tried. increases the iterations 6x
// var dirOrders = u.GetPermutations(dirNorth, dirSouth, dirWest, dirEast)
type visitedStatus struct {
timesVisited int
distanceFromStart int
}
type Day15 struct {
program u.IntcodeProgram
grid map[point]cellStatus
visited map[point]*visitedStatus
shortestPath []point
pos point
goalPos point
}
func (d *Day15) Parse() {
d.program = u.LoadIntcodeProgram("15p")
d.grid = map[point]cellStatus{
{First: 0, Second: 0}: cellStatusOpen,
}
d.visited = map[point]*visitedStatus{
{First: 0, Second: 0}: {timesVisited: 1},
}
d.shortestPath = []point{{}}
}
func (d Day15) Num() int {
return 15
}
func (d Day15) getPointInDirection(pos point, dir dirType) point {
target := pos
switch dir {
case dirNorth:
target.First--
case dirSouth:
target.First++
case dirWest:
target.Second--
case dirEast:
target.Second++
}
return target
}
func (d Day15) getCellTypeInDirection(pos point, dir dirType) (cellStatus, point) {
target := d.getPointInDirection(pos, dir)
return d.grid[target], target
}
func (d Day15) getAdjacentCellsOfType(pos point, cellType cellStatus) []point {
points := make([]point, 0, 4)
for i := dirFirst; i <= dirLast; i++ {
adjacentCell := d.getPointInDirection(pos, i)
if d.grid[adjacentCell] == cellType {
points = append(points, adjacentCell)
}
}
return points
}
func (d Day15) getDirToNextCellType(pos point, t cellStatus, maxNumVisited int, dirs []dirType) (dirType, point, error) {
for _, dir := range dirs {
cellInDirection, targetCell := d.getCellTypeInDirection(pos, dir)
if cellInDirection == t {
_, visitedTargetExists := d.visited[targetCell]
foundUnknown := t == cellStatusUnknown && !visitedTargetExists
foundOther := t != cellStatusUnknown && visitedTargetExists && d.visited[targetCell].timesVisited <= maxNumVisited
if foundUnknown || foundOther {
return dir, targetCell, nil
}
}
}
return dirFirst, point{}, fmt.Errorf("no %v tiles around %v", t, pos)
}
func (d *Day15) Draw() {
min := point{First: math.MaxInt, Second: math.MaxInt}
max := point{First: math.MinInt, Second: math.MinInt}
for p := range d.grid {
if p.First < min.First {
min.First = p.First
}
if p.First > max.First {
max.First = p.First
}
if p.Second < min.Second {
min.Second = p.Second
}
if p.Second > max.Second {
max.Second = p.Second
}
}
for x := min.First; x <= max.First; x++ {
for y := min.Second; y <= max.Second; y++ {
p := point{First: x, Second: y}
switch d.grid[p] {
case cellStatusGoal:
fmt.Printf("%s@%s", u.ColorBrightGreen, u.TextReset)
case cellStatusOpen:
if p == d.pos {
fmt.Print(u.BackgroundBrightRed)
} else if x == 0 && y == 0 {
fmt.Print(u.BackgroundYellow)
} else if u.ArrayContains(d.shortestPath, p) {
fmt.Print(u.BackgroundGreen)
} else if d.visited[p] != nil && d.visited[p].timesVisited > maxVisited {
fmt.Print(u.ColorYellow)
fmt.Print(u.BackgroundBlack)
} else if d.visited[p] != nil && d.visited[p].timesVisited > 1 {
fmt.Print(u.BackgroundMagenta)
} else {
fmt.Print(u.BackgroundBlue)
}
fmt.Printf(".%s", u.TextReset)
case cellStatusWall:
fmt.Print("█")
case cellStatusUnknown:
fmt.Print(" ")
}
}
fmt.Println()
}
}
func (d *Day15) markShortestPath() {
pos := d.goalPos
checkOffsets := []point{
{First: -1, Second: 0},
{First: 1, Second: 0},
{First: 0, Second: -1},
{First: 0, Second: 1},
}
checkPt := func(pt point) (bool, int) {
if v, exists := d.visited[pt]; exists && d.grid[pt] == cellStatusOpen {
return true, v.distanceFromStart
}
return false, math.MaxInt
}
d.shortestPath = []point{d.goalPos}
for pos.First != 0 || pos.Second != 0 {
lowestDist := math.MaxInt
lowestPoint := point{}
for _, pt := range checkOffsets {
newPt := point{First: pos.First + pt.First, Second: pos.Second + pt.Second}
if found, dist := checkPt(newPt); found && dist < lowestDist {
lowestDist = dist
lowestPoint = newPt
}
}
d.shortestPath = append(d.shortestPath, lowestPoint)
pos = lowestPoint
}
}
func (d *Day15) exploreFullMap() map[point]*visitedStatus {
grids := make([]map[point]cellStatus, 0, len(dirOrders))
goalVisited := d.visited
for _, dirOrder := range dirOrders {
d.program.Reset()
targetPos := point{}
nextDir := dirFirst
distFromStart := 0
d.pos = point{}
d.visited = map[point]*visitedStatus{
{First: 0, Second: 0}: {timesVisited: 1},
}
d.grid = map[point]cellStatus{
{First: 0, Second: 0}: cellStatusOpen,
}
d.program.RunIn(func(inputStep int) int64 {
var err error
nextDir, targetPos, err = d.getDirToNextCellType(d.pos, cellStatusUnknown, 0, dirOrder)
if err != nil {
// ensure we never try to go back into the trapped spot
d.visited[d.pos].timesVisited = maxVisited + 1
for x := 1; x <= maxVisited && err != nil; x++ {
nextDir, targetPos, err = d.getDirToNextCellType(d.pos, cellStatusOpen, x, dirOrder)
}
}
if err != nil {
// d.Draw()
// panic(err)
d.program.Stop()
}
return int64(nextDir)
}, func(val int64, state u.IntcodeProgramState) {
rVal := responseType(val)
p := d.getPointInDirection(d.pos, nextDir)
shouldMove := true
switch rVal {
case responseWall:
d.grid[p] = cellStatusWall
shouldMove = false
case responseSuccess:
d.grid[p] = cellStatusOpen
case responseFoundGoal:
d.grid[p] = cellStatusGoal
}
if shouldMove {
d.pos = targetPos
if d.visited[d.pos] == nil {
d.visited[d.pos] = &visitedStatus{}
distFromStart++
} else {
distFromStart--
}
d.visited[d.pos].timesVisited++
d.visited[d.pos].distanceFromStart = distFromStart
}
if rVal == responseFoundGoal {
// d.Draw()
d.goalPos = targetPos
goalVisited = d.visited
}
})
grids = append(grids, d.grid)
}
d.grid = map[point]cellStatus{
{First: 0, Second: 0}: cellStatusOpen,
}
for _, grid := range grids {
keys := u.MapKeys(grid)
for _, key := range keys {
d.grid[key] = grid[key]
}
}
return goalVisited
}
func (d *Day15) tagDistanceRecursive(pos, last point, dist int, distances map[point]int) {
distances[pos] = dist
for _, cell := range d.getAdjacentCellsOfType(pos, cellStatusOpen) {
if cell == last {
continue
}
d.tagDistanceRecursive(cell, pos, dist+1, distances)
}
}
func (d *Day15) Part1() string {
d.visited = d.exploreFullMap()
d.markShortestPath()
for _, visited := range d.visited {
visited.timesVisited = 1
}
d.pos = point{}
// d.Draw()
return fmt.Sprintf("Moves required to reach target: %s%d%s", u.TextBold, d.visited[d.goalPos].distanceFromStart, u.TextReset)
}
func (d *Day15) Part2() string {
startLoc := d.goalPos
distanceMap := map[point]int{startLoc: 0}
d.tagDistanceRecursive(startLoc, point{}, 0, distanceMap)
cellDistances := u.MapValues(distanceMap)
sort.Slice(cellDistances, func(i, j int) bool { return cellDistances[i] > cellDistances[j] })
return fmt.Sprintf("Time to fill the area with oxygen: %s%d%s minutes", u.TextBold, cellDistances[0], u.TextReset)
}

66
days/15_types_string.go Normal file
View File

@ -0,0 +1,66 @@
// Code generated by "stringer -type=cellStatus,responseType,dirType -output=15_types_string.go"; DO NOT EDIT.
package days
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[cellStatusUnknown-0]
_ = x[cellStatusWall-1]
_ = x[cellStatusOpen-2]
_ = x[cellStatusGoal-3]
}
const _cellStatus_name = "cellStatusUnknowncellStatusWallcellStatusOpencellStatusGoal"
var _cellStatus_index = [...]uint8{0, 17, 31, 45, 59}
func (i cellStatus) String() string {
if i < 0 || i >= cellStatus(len(_cellStatus_index)-1) {
return "cellStatus(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _cellStatus_name[_cellStatus_index[i]:_cellStatus_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[responseWall-0]
_ = x[responseSuccess-1]
_ = x[responseFoundGoal-2]
}
const _responseType_name = "responseWallresponseSuccessresponseFoundGoal"
var _responseType_index = [...]uint8{0, 12, 27, 44}
func (i responseType) String() string {
if i < 0 || i >= responseType(len(_responseType_index)-1) {
return "responseType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _responseType_name[_responseType_index[i]:_responseType_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[dirNorth-1]
_ = x[dirSouth-2]
_ = x[dirWest-3]
_ = x[dirEast-4]
}
const _dirType_name = "dirNorthdirSouthdirWestdirEast"
var _dirType_index = [...]uint8{0, 8, 16, 23, 30}
func (i dirType) String() string {
i -= 1
if i < 0 || i >= dirType(len(_dirType_index)-1) {
return "dirType(" + strconv.FormatInt(int64(i+1), 10) + ")"
}
return _dirType_name[_dirType_index[i]:_dirType_index[i+1]]
}

1
inputs/15p.txt Normal file
View File

@ -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,102,1,1034,1039,101,0,1036,1041,1001,1035,-1,1040,1008,1038,0,1043,102,-1,1043,1032,1,1037,1032,1042,1105,1,124,102,1,1034,1039,1001,1036,0,1041,1001,1035,1,1040,1008,1038,0,1043,1,1037,1038,1042,1105,1,124,1001,1034,-1,1039,1008,1036,0,1041,1001,1035,0,1040,1001,1038,0,1043,1002,1037,1,1042,1105,1,124,1001,1034,1,1039,1008,1036,0,1041,102,1,1035,1040,101,0,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,3,1032,1006,1032,165,1008,1040,33,1032,1006,1032,165,1101,0,2,1044,1105,1,224,2,1041,1043,1032,1006,1032,179,1101,1,0,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,37,1044,1106,0,224,1102,1,0,1044,1105,1,224,1006,1044,247,101,0,1039,1034,101,0,1040,1035,1001,1041,0,1036,1002,1043,1,1038,101,0,1042,1037,4,1044,1106,0,0,42,4,15,10,25,91,86,34,69,14,50,9,24,24,54,10,18,63,17,2,88,36,31,60,20,13,20,76,94,25,41,36,78,3,39,17,94,10,25,22,16,67,72,31,47,15,25,66,8,17,54,8,89,67,29,28,92,11,54,14,4,64,78,28,80,66,6,70,36,56,13,63,17,19,83,17,27,29,34,54,4,93,24,71,6,66,22,21,92,93,39,4,31,76,72,25,74,89,18,62,18,27,57,35,83,39,14,23,95,2,79,25,97,86,13,79,1,34,90,81,29,45,31,38,67,17,92,32,31,50,1,42,81,1,2,87,7,52,74,20,85,22,32,47,16,77,96,28,14,74,22,55,15,75,44,29,19,8,73,2,54,18,26,64,95,21,98,48,25,36,11,78,77,5,16,70,18,10,76,51,51,10,25,43,56,12,13,48,8,17,68,10,64,25,93,42,3,52,24,72,99,23,54,13,44,17,15,8,68,59,15,95,61,9,50,8,51,23,8,39,13,95,64,12,28,56,90,1,62,27,12,60,6,5,18,24,13,99,12,18,92,97,7,56,22,48,91,34,87,32,98,20,89,74,16,51,84,21,46,14,23,52,17,57,12,50,17,97,23,99,11,21,68,21,61,89,13,45,64,89,18,36,40,35,90,9,1,3,81,33,32,83,99,97,34,4,46,31,21,90,62,14,93,11,22,99,51,70,88,51,2,4,29,36,35,48,17,25,30,69,34,3,39,89,31,89,33,30,88,77,18,30,67,17,40,61,19,40,85,26,23,49,22,41,30,13,79,6,34,40,33,43,49,84,19,78,43,10,74,18,61,15,22,51,86,2,78,11,33,92,24,88,27,24,44,2,97,4,4,49,72,93,24,65,79,21,60,33,46,36,22,15,87,33,78,2,49,70,7,78,78,11,14,64,41,61,41,6,1,49,35,78,47,65,14,66,10,86,76,2,32,88,3,24,14,87,9,95,32,19,4,10,67,60,15,19,53,47,24,29,65,5,95,35,1,70,16,43,53,11,64,17,34,84,74,65,30,18,58,2,35,48,38,33,46,16,87,27,12,79,11,88,35,7,5,35,67,83,38,6,17,56,82,13,45,32,30,67,25,62,7,43,63,9,36,14,58,53,25,98,12,38,78,13,63,93,33,11,54,9,66,32,79,62,47,28,6,67,31,53,71,2,30,59,12,90,59,67,2,58,52,1,30,51,49,22,89,88,27,19,41,27,13,19,76,5,82,58,12,49,51,17,15,73,35,25,74,90,29,14,96,83,69,11,18,14,10,40,93,35,31,35,36,58,36,16,48,7,66,98,31,47,34,47,33,5,28,82,88,1,30,80,95,32,87,2,19,91,74,74,19,8,25,63,65,51,30,14,41,98,99,21,90,15,91,3,31,74,27,31,77,28,74,4,27,88,82,11,54,35,52,13,88,71,93,20,82,18,36,68,33,83,1,18,5,42,46,29,62,10,78,67,9,84,48,22,33,74,36,53,58,31,5,8,55,10,24,49,34,81,1,4,86,5,25,2,75,36,49,2,24,88,72,8,64,36,38,10,23,36,93,28,51,90,4,99,57,31,10,14,94,21,27,61,34,70,41,32,14,91,20,83,30,54,26,44,30,85,96,87,35,16,61,99,16,32,53,68,87,1,89,43,9,17,4,39,50,61,8,49,27,48,13,51,34,47,30,89,68,50,18,63,99,50,32,41,33,71,1,43,57,64,24,95,9,89,8,64,18,75,23,97,74,67,24,55,1,87,97,44,0,0,21,21,1,10,1,0,0,0,0,0,0

View File

@ -45,6 +45,7 @@ var dayMap = []day{
&days.Day12{}, &days.Day12{},
&days.Day13{}, &days.Day13{},
&days.Day14{}, &days.Day14{},
&days.Day15{},
} }
func main() { func main() {

View File

@ -24,9 +24,10 @@ const (
) )
type IntcodeProgram struct { type IntcodeProgram struct {
memory []int64 memory []int64
program []int64 program []int64
relativeBase int relativeBase int
haltRequested bool
} }
type IntcodeProgramState struct { type IntcodeProgramState struct {
@ -147,7 +148,7 @@ func (p *IntcodeProgram) RunIn(inputFunc ProvideInputFunc, outputFunc ReceiveOut
p.init() p.init()
inputsRequested := 0 inputsRequested := 0
for instructionPointer := 0; instructionPointer < len(p.program); { for instructionPointer := 0; instructionPointer < len(p.program) && !p.haltRequested; {
instruction := p.GetMemory(instructionPointer) instruction := p.GetMemory(instructionPointer)
instructionPointer++ instructionPointer++
@ -253,4 +254,10 @@ func (p *IntcodeProgram) RunIn(inputFunc ProvideInputFunc, outputFunc ReceiveOut
panic(fmt.Sprintf("exception executing program - unhandled opcode %d", opcode)) panic(fmt.Sprintf("exception executing program - unhandled opcode %d", opcode))
} }
} }
p.haltRequested = false
}
func (p *IntcodeProgram) Stop() {
p.haltRequested = true
} }

View File

@ -4,7 +4,7 @@ type Permutable interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
} }
func GetPermutations[T Permutable](arr []T) [][]T { func GetPermutations[T Permutable](arr ...T) [][]T {
var helper func([]T, int) var helper func([]T, int)
res := [][]T{} res := [][]T{}