mirror of
https://github.com/Luzifer/aoc2019.git
synced 2024-12-22 05:51:16 +00:00
Partial: Day 18
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
84e8a9afa0
commit
2820e2d148
3 changed files with 461 additions and 0 deletions
320
day18.go
Normal file
320
day18.go
Normal file
|
@ -0,0 +1,320 @@
|
|||
package aoc2019
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type day18TileType int
|
||||
|
||||
const (
|
||||
day18TileTypeWall day18TileType = iota
|
||||
day18TileTypeFloor
|
||||
day18TileTypeDoor
|
||||
day18TileTypeKey
|
||||
day18TileTypeActor
|
||||
)
|
||||
|
||||
type day18Tile struct {
|
||||
X, Y int
|
||||
Type day18TileType
|
||||
KeyLockID string
|
||||
|
||||
distFromActor int64
|
||||
}
|
||||
|
||||
func (d *day18Tile) updateBFSDistance(grid day18Grid, dist int64, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
if d.distFromActor < dist {
|
||||
// Do not update tiles which dist has been set already
|
||||
return
|
||||
}
|
||||
|
||||
// Set distance
|
||||
d.distFromActor = dist
|
||||
|
||||
// Seek adjacent tiles and update them too
|
||||
for _, t := range []*day18Tile{
|
||||
grid[grid.key(d.X-1, d.Y)],
|
||||
grid[grid.key(d.X+1, d.Y)],
|
||||
grid[grid.key(d.X, d.Y-1)],
|
||||
grid[grid.key(d.X, d.Y+1)],
|
||||
} {
|
||||
if t.Type == day18TileTypeWall || t.Type == day18TileTypeDoor {
|
||||
// Doors and walls are inaccessible objects and therefore
|
||||
// immediately stop distance progressing
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go t.updateBFSDistance(grid, dist+1, wg)
|
||||
}
|
||||
}
|
||||
|
||||
type day18Grid map[string]*day18Tile
|
||||
|
||||
func (d day18Grid) accessibleKeys() []string {
|
||||
var keys []string
|
||||
|
||||
for _, key := range d.getTilesByType(day18TileTypeKey) {
|
||||
if key.distFromActor < math.MaxInt64 {
|
||||
keys = append(keys, key.KeyLockID)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func (d day18Grid) bounds() (minX, minY, maxX, maxY int) {
|
||||
minX, minY = math.MaxInt64, math.MaxInt64
|
||||
|
||||
for _, t := range d {
|
||||
if t.X < minX {
|
||||
minX = t.X
|
||||
}
|
||||
if t.Y < minY {
|
||||
minY = t.Y
|
||||
}
|
||||
if t.X > maxX {
|
||||
maxX = t.X
|
||||
}
|
||||
if t.Y > maxY {
|
||||
maxY = t.Y
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d day18Grid) clone() day18Grid {
|
||||
var out = make(day18Grid)
|
||||
|
||||
for key, tile := range d {
|
||||
var tmpTile = &day18Tile{}
|
||||
*tmpTile = *tile
|
||||
out[key] = tmpTile
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (d day18Grid) getKeyByID(id string) *day18Tile {
|
||||
for _, key := range d.getTilesByType(day18TileTypeKey) {
|
||||
if key.KeyLockID == id {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d day18Grid) getPathDistances(path string, shortest *int64) (map[string]int64, error) {
|
||||
var (
|
||||
dist int64
|
||||
// Always work on a clone and leave the original grid intact
|
||||
grid = d.clone()
|
||||
out = make(map[string]int64)
|
||||
)
|
||||
|
||||
grid.updateBFSDistance()
|
||||
|
||||
// Apply movements
|
||||
for _, c := range path {
|
||||
key := grid.getKeyByID(string(c))
|
||||
moveDist, err := grid.move(key.X, key.Y)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to move")
|
||||
}
|
||||
dist += moveDist
|
||||
grid.updateBFSDistance()
|
||||
|
||||
if dist >= *shortest {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
remainingKeys := grid.getTilesByType(day18TileTypeKey)
|
||||
|
||||
if len(remainingKeys) == 0 {
|
||||
fmt.Printf("DBG %s = %d\n", path, dist)
|
||||
out[path] = dist
|
||||
if dist < *shortest {
|
||||
*shortest = dist
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
sort.Slice(remainingKeys, func(i, j int) bool { return remainingKeys[i].distFromActor < remainingKeys[j].distFromActor })
|
||||
|
||||
for _, k := range remainingKeys {
|
||||
if k.distFromActor == math.MaxInt64 {
|
||||
continue
|
||||
}
|
||||
|
||||
recDists, err := d.getPathDistances(path+k.KeyLockID, shortest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, dist := range recDists {
|
||||
out[key] = dist
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (d day18Grid) getTilesByType(t day18TileType) []*day18Tile {
|
||||
var out []*day18Tile
|
||||
|
||||
for _, tile := range d {
|
||||
if tile.Type == t {
|
||||
out = append(out, tile)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (day18Grid) key(x, y int) string { return fmt.Sprintf("%d:%d", x, y) }
|
||||
|
||||
func (d day18Grid) move(x, y int) (int64, error) {
|
||||
var (
|
||||
origin = d.getTilesByType(day18TileTypeActor)[0] // There MUST be only one actor
|
||||
target = d[d.key(x, y)]
|
||||
)
|
||||
|
||||
if target.distFromActor == math.MaxInt64 {
|
||||
return 0, errors.New("Distance not initialized / move to unreachable destination")
|
||||
}
|
||||
|
||||
if target.Type == day18TileTypeKey {
|
||||
for _, door := range d.getTilesByType(day18TileTypeDoor) {
|
||||
if door.KeyLockID == strings.ToUpper(target.KeyLockID) {
|
||||
// Unlock door, transform to floor
|
||||
door.Type = day18TileTypeFloor
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move the actor
|
||||
target.Type = day18TileTypeActor
|
||||
origin.Type = day18TileTypeFloor
|
||||
|
||||
return target.distFromActor, nil
|
||||
}
|
||||
|
||||
func (d day18Grid) print() {
|
||||
var minX, minY, maxX, maxY = d.bounds()
|
||||
|
||||
for y := minY; y <= maxY; y++ {
|
||||
for x := minX; x <= maxX; x++ {
|
||||
var t = d[d.key(x, y)]
|
||||
switch t.Type {
|
||||
case day18TileTypeActor:
|
||||
fmt.Print("@")
|
||||
case day18TileTypeDoor, day18TileTypeKey:
|
||||
fmt.Print(t.KeyLockID)
|
||||
case day18TileTypeFloor:
|
||||
fmt.Print(".")
|
||||
case day18TileTypeWall:
|
||||
fmt.Print("\u2588")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func (d day18Grid) updateBFSDistance() {
|
||||
// Reset distances
|
||||
for _, t := range d {
|
||||
t.distFromActor = math.MaxInt64
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
d.getTilesByType(day18TileTypeActor)[0].updateBFSDistance(d, 0, wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func day18ReadGrid(raw []byte) day18Grid {
|
||||
var (
|
||||
out = make(day18Grid)
|
||||
x, y int
|
||||
)
|
||||
|
||||
for _, c := range raw {
|
||||
var t = &day18Tile{X: x, Y: y, KeyLockID: string(c)}
|
||||
|
||||
switch {
|
||||
case c == '\n':
|
||||
x, y = 0, y+1
|
||||
continue
|
||||
case c == '#':
|
||||
t.Type = day18TileTypeWall
|
||||
case c == '.':
|
||||
t.Type = day18TileTypeFloor
|
||||
case c == '@':
|
||||
t.Type = day18TileTypeActor
|
||||
case 'a' <= c && c <= 'z':
|
||||
t.Type = day18TileTypeKey
|
||||
case 'A' <= c && c <= 'Z':
|
||||
t.Type = day18TileTypeDoor
|
||||
}
|
||||
|
||||
out[out.key(x, y)] = t
|
||||
x++
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func solveDay18Part1(inFile string) (int64, error) {
|
||||
raw, err := ioutil.ReadFile(inFile)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to read file")
|
||||
}
|
||||
|
||||
grid := day18ReadGrid(bytes.TrimSpace(raw))
|
||||
|
||||
grid.print()
|
||||
|
||||
// Calculate max heuristic distance by using always using shortest
|
||||
// single distance which will not be the shortest total distance
|
||||
// but set an upper limit for the heuristic
|
||||
cgrid := grid.clone()
|
||||
cgrid.updateBFSDistance()
|
||||
var minDist int64
|
||||
for len(cgrid.accessibleKeys()) > 0 {
|
||||
keys := cgrid.getTilesByType(day18TileTypeKey)
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i].distFromActor < keys[j].distFromActor })
|
||||
d, err := cgrid.move(keys[0].X, keys[0].Y)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Failed to move")
|
||||
}
|
||||
minDist += d
|
||||
cgrid.updateBFSDistance()
|
||||
}
|
||||
|
||||
log.Printf("Shortest distance for heuristic: %d", minDist)
|
||||
|
||||
dists, err := grid.getPathDistances("", &minDist)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to determine distances")
|
||||
}
|
||||
|
||||
fmt.Printf("%#v\n", dists)
|
||||
|
||||
return minDist, nil
|
||||
}
|
||||
|
||||
func solveDay18Part2(inFile string) (int64, error) { return 0, errors.New("Not implemented") }
|
81
day18_input.txt
Normal file
81
day18_input.txt
Normal file
|
@ -0,0 +1,81 @@
|
|||
#################################################################################
|
||||
#.#...#.....#...............#.....#v....#.........#p....#...#...................#
|
||||
#.#.#.#.#.#.#M#############.#.###.#####.#####.###.#.###.#.#.#################.#.#
|
||||
#..n#...#.#...#.#...........#...#.......#.....#.....#.#...#...#.......#...#...#.#
|
||||
#########.#####.#.###########.#.#######.#.###########.#######.#C#####.#.#.#.###.#
|
||||
#.........#.#.....#.........#.#.#.......#...........#...........#...#.#.#...#...#
|
||||
#.#########.#.#########.#####.#W###.###############.###.#########.###.#.#####.###
|
||||
#.Y...#...#...#.......#z......#...#.#...#...#.....#..d#.#.#..x..#...#.#.#...#.#.#
|
||||
#####.#.#.#.###.#.###.#####.#####.#.#.#.#.#.#.###.###.#.#.#.###.#.#.#.#.#.#.#.#.#
|
||||
#.....#.#...#...#.#.#.....#...#...#.#.#.#.#...#.......#...#.#...#.#.....#.#...#.#
|
||||
#.#######.#######.#.#####.#####.#####.#.#.#############.###.#.###.#########.###.#
|
||||
#...#...#.#...#...#.#...#.....#...B...#.#.....#...#.....#...#.#.....#....t#.....#
|
||||
#.#.#.#.#.#.#.#.###.#.#.#####.#.#######.#.###.#.#.###.###.###.#.#####.###.#####N#
|
||||
#.#...#.#...#.#.#.....#...#...#.#...#...#...#.#.#...#.#...#...#...#...#.#...#.#.#
|
||||
#.#####.#####.#.#########.#.###.#.#.#.#.###.#.#.###.###.###.#######.###.###.#.#.#
|
||||
#.#...#.#.......#...#.....#...#...#.#.#.#.#.#...#.#.....#...#.....#.#...#...#.#.#
|
||||
#.#.###.#########.#.#.###.###.#####.#.#.#.#.#####.#######.###.###.#.#.###.###.#.#
|
||||
#.#...#.#...#.....#.#...#...#.#...#.#.#.#.#.#.....#.....#...#.#.#...#...#.#...#.#
|
||||
#.###.#.#.#.#.#####.#.#.#####.#.###.#.###.#.#.###.###.#.###.#.#.#####.#.#J#E###.#
|
||||
#.....#.#.#...#...#.#.#.....#.#.....#...#.#.#.#.......#.#...#.#...#...#...#.....#
|
||||
#######.#.#####.###.#######.#.#########.#.#.#.#########.#.###.#.#.#.#########.###
|
||||
#.......#.#.....#.I.#.....#.............#...#...#.....#.#...#.#.#.#.........#...#
|
||||
#.#######.###.###.#.#####.#.###########.#.###.#.###.###.###.#####.#########.###.#
|
||||
#.......#...#.#...#.....#.#.#.......#...#.#...#.#...#.....#.........#.....#.#...#
|
||||
#.#####.###.#.#.#######.#.###.#####.#.###.#####.#.###.#######.#####.#.###.#.###.#
|
||||
#.#...#...#.#.#.......#.#.....#.#...#...#.#...#.#...#u#.....#...#...#.#.#.#...#.#
|
||||
#.###.#.###.#.#######.#.#######.#.#####.#.#.#.#.#.#.#.#.#.#.###.#####.#.#.###.#.#
|
||||
#...#.#...#.....#.....#.#.....#...#...#.#...#.#.#.#...#.#.#...#.#.....#.#.#...#.#
|
||||
###.#.###.#####.#.#####.#.#.#.#.###.#.#.#####.#.#.#####.#.#.###.#.#####.#.#.###.#
|
||||
#...#...#.......#.#.....#.#.#.#.....#.#.#.....#.#.#.....#.#.#...#.....#.#.#.#..k#
|
||||
#.###.###########.#.#######.#.#######.#.#.#####.###.#####.###.#.#####.#.#.#.#.###
|
||||
#...#...........#.#.....#...#.......#.#.#...#.........#...#...#.#.....#.#...#.#.#
|
||||
###.#.###.###.###.#####.#.#.#######.#.#.###.#.#########.#.#.###.#.#####.#####.#.#
|
||||
#.#.#...#...#.#...#...#.#.#.#.....#.#.#.#.#.#...#.......#.#.#...#...#.....#...#.#
|
||||
#.#.###.###.#.#.###.#.#.#.###.###.###.###.#.#####.#######.#.#.#####.#.#.#.#.###.#
|
||||
#.#.#.....#.#.#.....#g#...#...#.#...#...#.#.......#...#...#.#...#.#.#.#.#.#.....#
|
||||
#.#.#######.#.#######.###.#.###.###.###.#.#########.###.###.###.#.#.#.#.#######.#
|
||||
#...#.....#.#.#.....#.#...#.......#.#...#.....#.......#.#.#...#...#.#.#.......#.#
|
||||
#.###.###.#.#.#.###.#.###########.#.#.###.###.#.#####.#.#.###.#####.#.#######.#.#
|
||||
#.....#.....#...#...#.A...........#.........#.......#.R.....#.......#.......#...#
|
||||
#######################################.@.#######################################
|
||||
#.....#.........#...........#......r#...........#.#...#.............#.......#...#
|
||||
###.###.#.#######.#.#######.#.#####.###.#.#.###.#.#.#.#.#######.###.#.#####.###.#
|
||||
#...#...#.........#...#...#.#.....#.....#.#...#...#.#...#...#...#.#.#.....#.....#
|
||||
#.###.###############.#.###.#.###.#####.#.###.#####.#######.#.###.#.#####.#######
|
||||
#.....#.......#.#.....#...#.#.#...#.....#.#.#.#.....#.......#.#...#.....#.......#
|
||||
#.#######.###.#.#.#######.#.###.#.#######.#.#.#.#########.###.#.#######G#.#####.#
|
||||
#.....#...#.#...#.#.......#...#.#.#.....#...#.#.#.....#..a#...#.#.....#.#i....#.#
|
||||
#####.#.###.###.#.###.#.#####.#.###.###.#####.#.###.#.#.#.#.###.#.###.#.#######.#
|
||||
#...#.#.#.....#.#...#.#.#.....#.....#...#...#.......#...#.#.#.....#.#.........#.#
|
||||
#.#.#.#.#.#####.###.#.###.###########.###.#.#.#############.#.#####.#########.#.#
|
||||
#.#.#.#.#.......#...#.....#.....#...#...#.#.#.#.............#.......#...#.....O.#
|
||||
#.#.#.#.#########.#######.#.###.#.#.###.#.#.###.###########.#########.#.#######.#
|
||||
#.#.#e#.........#.#.....#...#...#.#.....#.#.#...#...........#.......#.#.....#...#
|
||||
###.#.#########.#.#.###.#####P###.#######.#.#.#########.#####.#####.#.#####.#.###
|
||||
#...#.......#...#...#.#.#...#...#...#...#.#...#.......#.#w....#f..#...#...#.#...#
|
||||
#.#.#######.#.#######.#.#.#####.#.#.#.#.#.#.###.#####.###.###.#.#.#####.#.#.#####
|
||||
#.#.#...#...#.........#.#...#...#.#...#.#.#.#.......#.#...#...#.#...#...#.#.#...#
|
||||
#.#.#.###.#######.###.#.#.#.#.###.#####.#.###.#######.#.#######.###.#.###.#.#.#.#
|
||||
#.#...#...#.....#...#.#.#.#...#..o..#...#...#...#...#.#...#...F.#...#...#.#...#.#
|
||||
#.#####.###.###.###.###.#####.#####.#.#.#.#.#.###.#.#.###.#.#####L###.#.#.#####.#
|
||||
#.......#...#.#.#...#.#.....#.....#.#.#.#.#...#...#.#.#...#...#...#...#.#.....#.#
|
||||
#K#######.#.#.#.#.###.#####.#####X###.#.#######.###.#.#.#######.#####.#.#######.#
|
||||
#....j..#.#.#.....#...#.....#...#...#.#.#.......#...#.#.....#...#.S.#.#.........#
|
||||
#######.###.#####.#.###.#####.#.###.#.###.#######.###.#####.#.###.#.#.###########
|
||||
#.....#...#.....#.#...#.......#...#.#...#.....#.#.#.......#.#l....#.#.#...#.....#
|
||||
#T#.#####.#####.#####.#.#########.#.###.#.###.#.#.#####.#.#.#######.#.#.###.###.#
|
||||
#.#.....#.#.....#.....#.#.......#.#.#...#c#.#.#.....#...#.#...Z.#...#.#.....#...#
|
||||
#.###.###.#.#.###.#####.#.#####.#.#.#.#.#.#.#.#####.#.#######.###.###.#.#####.#.#
|
||||
#...#...#...#.#...#...#...#...#.#.#.#.#.#...#.....#...#.....#.#s..#...#.#.#...#.#
|
||||
###.###.#.#####.###.###.###.#.###.#.#.###.#######.###.#.#.###.#.###.###Q#.#.#####
|
||||
#.#.#.#.#.#...#...#...#.#...#.....#.#...#.#.....#.#...#.#.#...#.V.#.#...#.#.....#
|
||||
#.#.#.#.#.#.#.###.#.#.#.#.#########.###.#.#.###.#.#####.#.#.#####.#.#.###.#####.#
|
||||
#...#.#...#.#...#.#.#.#.#...#...#...#...#.#...#y..#...#.#.#.......#.#.#.......#.#
|
||||
#.###.#####.###.#.###.#.###.#.###.###.#.#####.#####.#.#.#.#########.#.#.#.#####.#
|
||||
#.#.....#...#.#...#...#.#...#.#.D.#.U.#.#.....#...#.#.#.#.......#...#..b#.#.....#
|
||||
#.#####.#.###.#####.###.#.###.#.###.###.#.#####.#.#.#.#.#######.#.#######.#.###.#
|
||||
#.H...#.#...#...........#.#...#.#.....#.#m......#...#...#.......#.#...#...#.#...#
|
||||
#####.#.###.#############.#.#.#.#####.#.#.###############.#######.#.#.#####.#.###
|
||||
#..q......#...............#.#.........#.#...............#...........#.......#..h#
|
||||
#################################################################################
|
60
day18_test.go
Normal file
60
day18_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package aoc2019
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDay18GetDistances(t *testing.T) {
|
||||
grid := day18ReadGrid([]byte("########################\n#...............b.C.D.f#\n#.######################\n#.....@.a.B.c.d.A.e.F.g#\n########################"))
|
||||
|
||||
var maxInt int64 = math.MaxInt64
|
||||
|
||||
dists, err := grid.getPathDistances("", &maxInt)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPathDistances caused an error: %s", err)
|
||||
}
|
||||
|
||||
if d := dists["bacdfeg"]; d != 132 {
|
||||
t.Errorf("Invalid distance for bacdfeg: exp=132 got=%d", d)
|
||||
}
|
||||
|
||||
grid = day18ReadGrid([]byte(`#################
|
||||
#i.G..c...e..H.p#
|
||||
########.########
|
||||
#j.A..b...f..D.o#
|
||||
########@########
|
||||
#k.E..a...g..B.n#
|
||||
########.########
|
||||
#l.F..d...h..C.m#
|
||||
#################`))
|
||||
maxInt = math.MaxInt64
|
||||
|
||||
dists, err = grid.getPathDistances("", &maxInt)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPathDistances caused an error: %s", err)
|
||||
}
|
||||
|
||||
if d := dists["afbjgnhdloepcikm"]; d != 136 {
|
||||
t.Errorf("Invalid distance for bacdfeg: exp=136 got=%d", d)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCalculateDay18_Part1(t *testing.T) {
|
||||
res, err := solveDay18Part1("day18_input.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Day 18 solver failed: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Solution Day 18 Part 1: %d", res)
|
||||
}
|
||||
|
||||
func TestCalculateDay18_Part2(t *testing.T) {
|
||||
res, err := solveDay18Part2("day18_input.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Day 18 solver failed: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Solution Day 18 Part 2: %d", res)
|
||||
}
|
Loading…
Reference in a new issue