2020-day20: OOF, monster (hah) backtracking algo, and then some...

This commit is contained in:
alexchao26
2020-12-20 15:35:46 -05:00
parent 228a8cda63
commit e1646bfdfd
3 changed files with 385 additions and 17 deletions
+259 -15
View File
@@ -3,9 +3,11 @@ package main
import (
"flag"
"fmt"
"math"
"strings"
"github.com/alexchao26/advent-of-code-go/mathutil"
"github.com/alexchao26/advent-of-code-go/algos"
"github.com/alexchao26/advent-of-code-go/util"
)
@@ -15,32 +17,274 @@ func main() {
flag.Parse()
fmt.Println("Running part", part)
var ans int
if part == 1 {
ans := part1(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
ans = part1(util.ReadFile("./input.txt"))
} else {
ans := part2(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
ans = part2(util.ReadFile("./input.txt"))
}
fmt.Println("Output:", ans)
}
func part1(input string) int {
parsed := parseInput(input)
_ = parsed
tiles := parseTilesFromInput(input)
edgeSize := int(math.Sqrt(float64(len(tiles))))
return 0
assembledTiles := backtrackAssemble(tiles, nil, map[int]bool{})
// return product of corners
product := assembledTiles[0][0].id
product *= assembledTiles[0][edgeSize-1].id
product *= assembledTiles[edgeSize-1][0].id
product *= assembledTiles[edgeSize-1][edgeSize-1].id
return product
}
func part2(input string) int {
return 0
tiles := parseTilesFromInput(input)
edgeSize := int(math.Sqrt(float64(len(tiles))))
assembledTiles := backtrackAssemble(tiles, nil, map[int]bool{})
// remove ALL borders from all tiles
for r := range assembledTiles {
for c, cell := range assembledTiles[r] {
assembledTiles[r][c].contents = removeBordersFromGrid(cell.contents)
}
}
func parseInput(input string) (ans []int) {
lines := strings.Split(input, "\n")
for _, l := range lines {
ans = append(ans, mathutil.StrToInt(l))
// generate image from assembledTiles...
// assembledTiles is effectively a 2D grid where each cell is a 2D grid representing a tile
// there has to be an easier way to "flatten" a 4D grid into a 2D grid...
var image [][]string
for bigRow := 0; bigRow < edgeSize; bigRow++ {
for subRow := 0; subRow < len(assembledTiles[0][0].contents); subRow++ {
image = append(image, []string{})
for bigCol := 0; bigCol < edgeSize; bigCol++ {
subLine := assembledTiles[bigRow][bigCol].contents[subRow]
image[len(image)-1] = append(image[len(image)-1], subLine...)
}
}
}
// get the coordinates of all monsters by iterating over all possible
// orientations of the image
var monsterCoords [][2]int
for _, opt := range generateGridOrientations(image) {
monsterCoords = findMonsterCoords(opt)
// assuming there's only one orientation of image with valid monsters
if len(monsterCoords) > 0 {
image = opt
break
}
}
// modify all monster coordinates to "O" characters (anything but "#")
for _, coord := range monsterCoords {
image[coord[0]][coord[1]] = "O"
}
// count up remaining "#" cells
var roughWatersCount int
for _, row := range image {
for _, cell := range row {
if cell == "#" {
roughWatersCount++
}
}
}
return roughWatersCount
}
type tile struct {
contents [][]string
id int
}
func parseTilesFromInput(input string) []*tile {
ans := []*tile{}
for _, block := range strings.Split(input, "\n\n") {
split := strings.Split(block, "\n")
var tileID int
_, err := fmt.Sscanf(split[0], "Tile %d:", &tileID)
if err != nil {
panic(err)
}
var contents [][]string
for _, line := range split[1:] {
contents = append(contents, strings.Split(line, ""))
}
ans = append(ans, &tile{id: tileID, contents: contents})
}
return ans
}
func generateGridOrientations(grid [][]string) [][][]string {
var options [][][]string
for i := 0; i < 2; i++ {
for j := 0; j < 4; j++ {
options = append(options, grid)
grid = algos.RotateStringGrid(grid)
}
grid = algos.MirrorStringGrid(grid)
}
// note: there will likely be duplicates in there... but that's fine...
return options
}
func backtrackAssemble(tiles []*tile, assembledTiles [][]*tile, usedIndices map[int]bool) [][]*tile {
// pray it's a square...
edgeSize := int(math.Sqrt(float64(len(tiles))))
if assembledTiles == nil {
assembledTiles = make([][]*tile, edgeSize)
for i := 0; i < edgeSize; i++ {
assembledTiles[i] = make([]*tile, edgeSize)
}
}
// iterate through all cells, skipping cells that have already been set
for row := 0; row < edgeSize; row++ {
for col := 0; col < edgeSize; col++ {
// skip cells that have already been assigned
if assembledTiles[row][col] == nil {
// iterate over all available tiles (skip ones that are tagged "used")
for i, t := range tiles {
if !usedIndices[i] {
// iterate over the OPTIONS for a particular tile, i.e. all 8 images of it...
for _, opt := range generateGridOrientations(t.contents) {
// check if setting this tile is okay with (if applicable) tiles above
// and to the left
if row != 0 { // check above
currentTopRow := getRow(opt, true)
bottomOfAbove := getRow(assembledTiles[row-1][col].contents, false)
// if they don't match, continue onto next option b/c this one doesn't match
if currentTopRow != bottomOfAbove {
continue
}
}
if col != 0 { // check left, same logic checking above
currentLeftCol := getCol(opt, true)
rightColOfLeft := getCol(assembledTiles[row][col-1].contents, false)
if currentLeftCol != rightColOfLeft {
continue
}
}
// set tile, mark tile as used, recurse
t.contents = opt // side effects apply b/c t is a pointer
assembledTiles[row][col] = t
// if non-nil response, relay the return value up the call stack
usedIndices[i] = true
recurseResult := backtrackAssemble(tiles, assembledTiles, usedIndices)
if recurseResult != nil {
return recurseResult
}
// backtrack if nil response from recursing
assembledTiles[row][col] = nil
usedIndices[i] = false
}
}
}
// Note: this is a key part of backtracking, to escape if there are
// no valid options for to set for this row/col
if assembledTiles[row][col] == nil {
return nil
}
}
}
}
// if entire loop finishes, that means every cell has been assigned
// return the assembled tiles to collapse the call stack
return assembledTiles
}
// helper functions to get a string (easily comparable) of a single side
func getCol(grid [][]string, firstCol bool) string {
var str string
for i := range grid {
if firstCol {
str += grid[i][0]
} else {
str += grid[i][len(grid[0])-1]
}
}
return str
}
func getRow(grid [][]string, firstRow bool) string {
var str string
for i := range grid[0] {
if firstRow {
str += grid[0][i]
} else {
str += grid[len(grid)-1][i]
}
}
return str
}
func removeBordersFromGrid(grid [][]string) [][]string {
var result [][]string
for i := 1; i < len(grid)-1; i++ {
result = append(result, []string{})
for j := 1; j < len(grid[0])-1; j++ {
result[i-1] = append(result[i-1], grid[i][j])
}
}
return result
}
// returns all coordinates that make up any valid monsters...
// valid monsters are the '#' like so... dots are for visual effect
// ..#.
// #....##....##....###
// .#..#..#..#..#..#...
var monster = ` #
# ## ## ###
# # # # # # `
func findMonsterCoords(image [][]string) [][2]int {
var monsterOffsets [][2]int
var monsterHeight, monsterLength int
for r, line := range strings.Split(monster, "\n") {
for c, char := range line {
if char == '#' {
monsterOffsets = append(monsterOffsets, [2]int{r, c})
}
monsterLength = c + 1
}
monsterHeight++
}
// determine the top left corners of a found monster
var monsterStartingCoords [][2]int
for r := 0; r < len(image)-monsterHeight+1; r++ {
for c := 0; c < len(image[0])-monsterLength+1; c++ {
monsterFound := true
for _, diff := range monsterOffsets {
rowToCheck := r + diff[0]
colToCheck := c + diff[1]
if image[rowToCheck][colToCheck] != "#" {
monsterFound = false
}
}
if monsterFound {
monsterStartingCoords = append(monsterStartingCoords, [2]int{r, c})
}
}
}
// generate a list of all the coordinates that are monsters
var monsterCoords [][2]int
for _, startingCoord := range monsterStartingCoords {
for _, diff := range monsterOffsets {
monsterCoords = append(monsterCoords, [2]int{startingCoord[0] + diff[0], startingCoord[1] + diff[1]})
}
}
return monsterCoords
}
+114 -2
View File
@@ -2,6 +2,8 @@ package main
import (
"testing"
"github.com/alexchao26/advent-of-code-go/util"
)
func Test_part1(t *testing.T) {
@@ -10,7 +12,8 @@ func Test_part1(t *testing.T) {
input string
want int
}{
// {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER},
{"example", example, 20899048083289},
{"actual", util.ReadFile("input.txt"), 29125888761511},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -27,7 +30,8 @@ func Test_part2(t *testing.T) {
input string
want int
}{
// {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER},
{"example", example, 273},
{"actual", util.ReadFile("input.txt"), 2219},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -37,3 +41,111 @@ func Test_part2(t *testing.T) {
})
}
}
var example = `Tile 2311:
..##.#..#.
##..#.....
#...##..#.
####.#...#
##.##.###.
##...#.###
.#.#.#..##
..#....#..
###...#.#.
..###..###
Tile 1951:
#.##...##.
#.####...#
.....#..##
#...######
.##.#....#
.###.#####
###.##.##.
.###....#.
..#.#..#.#
#...##.#..
Tile 1171:
####...##.
#..##.#..#
##.#..#.#.
.###.####.
..###.####
.##....##.
.#...####.
#.##.####.
####..#...
.....##...
Tile 1427:
###.##.#..
.#..#.##..
.#.##.#..#
#.#.#.##.#
....#...##
...##..##.
...#.#####
.#.####.#.
..#..###.#
..##.#..#.
Tile 1489:
##.#.#....
..##...#..
.##..##...
..#...#...
#####...#.
#..#.#.#.#
...#.#.#..
##.#...##.
..##.##.##
###.##.#..
Tile 2473:
#....####.
#..#.##...
#.##..#...
######.#.#
.#...#.#.#
.#########
.###.#..#.
########.#
##...##.#.
..###.#.#.
Tile 2971:
..#.#....#
#...###...
#.#.###...
##.##..#..
.#####..##
.#..####.#
#..#.#..#.
..####.###
..#.#.###.
...#.#.#.#
Tile 2729:
...#.#.#.#
####.#....
..#.#.....
....#..#.#
.##..##.#.
.#.####...
####.#.#..
##.####...
##..#.##..
#.##...##.
Tile 3079:
#.#.#####.
.#..######
..#.......
######....
####.#..#.
.#...#.##.
#.#####.##
..#.###...
..#.......
..#.###...`
+12
View File
@@ -0,0 +1,12 @@
package algos
// MirrorStringGrid returns the grid mirrored over the y-axis (i.e. left to right)
func MirrorStringGrid(grid [][]string) (flipped [][]string) {
for i := range grid {
flipped = append(flipped, []string{})
for j := len(grid[i]) - 1; j >= 0; j-- {
flipped[i] = append(flipped[i], grid[i][j])
}
}
return flipped
}