Day 20: Part 1 and 2

Signed-off-by: James Griffin <james@unsupervised.ca>
This commit is contained in:
2020-12-20 14:09:14 -04:00
parent 34c05492c5
commit 85cfd4b736
6 changed files with 2518 additions and 3 deletions

10
main.go
View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/thatguygriff/aoc2020/nineteen"
"github.com/thatguygriff/aoc2020/twenty"
)
func main() {
@@ -80,6 +80,10 @@ func main() {
// fmt.Println(eighteen.PartTwo())
// Day 19
fmt.Println(nineteen.PartOne())
fmt.Println(nineteen.PartTwo())
// fmt.Println(nineteen.PartOne())
// fmt.Println(nineteen.PartTwo())
// Day 20
fmt.Println(twenty.PartOne())
fmt.Println(twenty.PartTwo())
}

599
twenty/day_twenty.go Normal file
View File

@@ -0,0 +1,599 @@
package twenty
import (
"bufio"
"fmt"
"math"
"os"
"strings"
)
type tile struct {
id int
something [][]bool
edges []string
matches map[int]bool
}
type message struct {
tiles []tile
grid [][]*tile
image [][]bool
}
func (t *tile) edge(side string) string {
border := ""
switch side {
case "top":
for _, c := range t.something[0] {
if c {
border += "#"
} else {
border += "."
}
}
case "bottom":
for _, c := range t.something[9] {
if c {
border += "#"
} else {
border += "."
}
}
case "left":
for _, row := range t.something {
if row[0] {
border += "#"
} else {
border += "."
}
}
case "right":
for _, row := range t.something {
if row[9] {
border += "#"
} else {
border += "."
}
}
}
return border
}
func (t *tile) flip() {
flipped := [][]bool{}
flipped = make([][]bool, 10)
for i := 0; i < 10; i++ {
flipped[i] = make([]bool, 10)
}
for y := 0; y < 10; y++ {
for x := 0; x < 10; x++ {
flipped[y][x] = t.something[9-y][9-x]
}
}
t.something = flipped
}
func (t *tile) flipY() {
flipped := [][]bool{}
flipped = make([][]bool, 10)
for i := 0; i < 10; i++ {
flipped[i] = make([]bool, 10)
}
for y := 0; y < 10; y++ {
for x := 0; x < 10; x++ {
flipped[y][x] = t.something[9-y][x]
}
}
t.something = flipped
}
func (t *tile) rotate() {
rotated := [][]bool{}
rotated = make([][]bool, 10)
for i := 0; i < 10; i++ {
rotated[i] = make([]bool, 10)
}
for y := 0; y < 10; y++ {
for x := 0; x < 10; x++ {
rotated[x][9-y] = t.something[y][x]
}
}
t.something = rotated
}
func (m *message) load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
m.tiles = []tile{}
scanner := bufio.NewScanner(file)
var t *tile
for scanner.Scan() {
if strings.Contains(scanner.Text(), "Tile") {
t = &tile{}
count, err := fmt.Sscanf(scanner.Text(), "Tile %4d:", &t.id)
if count != 1 || err != nil {
return fmt.Errorf("Unable to parse %s", scanner.Text())
}
continue
}
if scanner.Text() == "" {
m.tiles = append(m.tiles, *t)
t = nil
continue
}
if t != nil {
row := []bool{}
for _, c := range scanner.Text() {
switch string(c) {
case ".":
row = append(row, false)
case "#":
row = append(row, true)
}
}
t.something = append(t.something, row)
}
}
if t != nil {
m.tiles = append(m.tiles, *t)
}
for i := 0; i < len(m.tiles); i++ {
m.tiles[i].matches = map[int]bool{}
m.tiles[i].edges = []string{}
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("top"))
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("bottom"))
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("left"))
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("right"))
m.tiles[i].flip()
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("top"))
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("bottom"))
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("left"))
m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("right"))
m.tiles[i].flip()
}
gridSize := int(math.Sqrt(float64(len(m.tiles))))
m.grid = make([][]*tile, gridSize)
for i := 0; i < gridSize; i++ {
m.grid[i] = make([]*tile, gridSize)
}
m.image = make([][]bool, gridSize*8)
for i := 0; i < gridSize*8; i++ {
m.image[i] = make([]bool, gridSize*8)
}
return nil
}
func (m *message) findCorners() []tile {
// Compute neighbours
for i := 0; i < len(m.tiles); i++ {
for j := 0; j < len(m.tiles); j++ {
if i == j {
continue
}
for _, iEdge := range m.tiles[i].edges {
for _, jEdge := range m.tiles[j].edges {
if iEdge == jEdge {
m.tiles[i].matches[m.tiles[j].id] = true
}
}
}
}
}
corners := []tile{}
// Find neighbours with only 2 matches
for i := 0; i < len(m.tiles); i++ {
if len(m.tiles[i].matches) == 2 {
corners = append(corners, m.tiles[i])
}
}
return corners
}
func (m *message) getTile(id int) *tile {
for _, t := range m.tiles {
if t.id == id {
return &t
}
}
return nil
}
func (m *message) inGrid(id int) bool {
for y := 0; y < len(m.grid); y++ {
for x := 0; x < len(m.grid); x++ {
if m.grid[y][x] != nil {
if id == m.grid[y][x].id {
return true
}
}
}
}
return false
}
func (m *message) layout() bool {
corners := m.findCorners()
for i := range corners {
m.grid[0][0] = &corners[i]
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
m.grid[0][0].flip()
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
if m.placeNeighbours(0, 0) {
return true
}
m.grid[0][0] = &corners[i]
m.grid[0][0].rotate()
if m.placeNeighbours(0, 0) {
return true
}
}
return false
}
func (m *message) placeNeighbours(x, y int) bool {
allGood := true
if x+1 >= len(m.grid) && y+1 >= len(m.grid) {
return true
}
possibleTiles := []tile{}
for id := range m.grid[y][x].matches {
if !m.inGrid(id) {
possibleTiles = append(possibleTiles, *m.getTile(id))
}
}
if x+1 < len(m.grid) {
if m.grid[y][x+1] == nil {
right := m.grid[y][x].edge("right")
found := false
for _, p := range possibleTiles {
for _, match := range p.edges {
if match == right {
found = true
rfound := false
for i := 0; i < 4; i++ {
if right == p.edge("left") {
found = true
rfound = true
m.grid[y][x+1] = &p
allGood = m.placeNeighbours(x+1, y)
break
}
p.rotate()
}
if rfound {
break
}
p.flipY()
for i := 0; i < 4; i++ {
if right == p.edge("left") {
found = true
m.grid[y][x+1] = &p
allGood = m.placeNeighbours(x+1, y)
break
}
p.rotate()
}
}
}
if found {
break
}
}
allGood = found
} else {
allGood = m.grid[y][x].edge("right") == m.grid[y][x+1].edge("left")
}
}
if y+1 < len(m.grid) {
bottom := m.grid[y][x].edge("bottom")
found := false
for _, p := range possibleTiles {
for _, match := range p.edges {
if match == bottom {
found = true
rfound := false
for i := 0; i < 4; i++ {
if bottom == p.edge("top") {
found = true
rfound = true
m.grid[y+1][x] = &p
allGood = m.placeNeighbours(x, y+1)
break
}
p.rotate()
}
if rfound {
break
}
p.flipY()
for i := 0; i < 4; i++ {
if bottom == p.edge("top") {
found = true
m.grid[y+1][x] = &p
allGood = m.placeNeighbours(x, y+1)
break
}
p.rotate()
}
}
}
if found {
break
}
allGood = false
}
}
if !allGood {
m.grid[y][x] = nil
}
return allGood
}
func (m *message) combineTiles() bool {
for y := 0; y < len(m.grid); y++ {
for x := 0; x < len(m.grid); x++ {
if m.grid[y][x] == nil {
return false
}
}
}
for y := 0; y < len(m.grid); y++ {
for x := 0; x < len(m.grid); x++ {
for tY := 1; tY < len(m.grid[y][x].something)-1; tY++ {
for tX := 1; tX < len(m.grid[y][x].something[tY])-1; tX++ {
imageY := (y * (len(m.grid[y][x].something) - 2)) + (tY - 1)
imageX := (x * (len(m.grid[y][x].something[tY]) - 2)) + (tX - 1)
m.image[imageY][imageX] = m.grid[y][x].something[tY][tX]
}
}
}
}
return true
}
func (m *message) rotateImage() {
rotated := [][]bool{}
rotated = make([][]bool, len(m.image))
for i := 0; i < len(m.image); i++ {
rotated[i] = make([]bool, len(m.image))
}
for y := 0; y < 10; y++ {
for x := 0; x < 10; x++ {
rotated[x][9-y] = m.image[y][x]
}
}
m.image = rotated
}
func (m *message) flipImage() {
flipped := [][]bool{}
flipped = make([][]bool, len(m.image))
for i := 0; i < len(m.image); i++ {
flipped[i] = make([]bool, len(m.image))
}
for y := 0; y < 10; y++ {
for x := 0; x < 10; x++ {
flipped[y][x] = m.image[9-y][x]
}
}
m.image = flipped
}
func (m *message) printGrid() {
output := ""
for y := 0; y < len(m.grid); y++ {
for tY := 0; tY < 10; tY++ {
for x := 0; x < len(m.grid[y]); x++ {
for tX := 0; tX < 10; tX++ {
if m.grid[y][x].something[tY][tX] {
output += "#"
} else {
output += "."
}
}
}
output += "\n"
}
}
fmt.Printf(output)
}
func (m *message) printImage() {
output := ""
for _, row := range m.image {
for _, val := range row {
if val {
output += "#"
} else {
output += "."
}
}
output += "\n"
}
fmt.Printf(output)
}
func (m *message) alignMonsters() bool {
for i := 0; i < 4; i++ {
count := m.seaMonsters()
if count > 0 {
return true
}
m.rotateImage()
}
m.flipImage()
for i := 0; i < 4; i++ {
count := m.seaMonsters()
if count > 0 {
return true
}
m.rotateImage()
}
return false
}
func (m *message) cornerProduct() int {
corners := m.findCorners()
if len(corners) != 4 {
return -1
}
product := 1
for _, tile := range corners {
product *= tile.id
}
return product
}
func (m *message) seaMonsters() int {
monsters := 0
for y := 0; y < len(m.image)-2; y++ {
for x := 0; x < len(m.image[0])-19; x++ {
// Check if this is a valid seamonster
if m.image[y][x+18] &&
m.image[y+1][x] &&
m.image[y+1][x+5] &&
m.image[y+1][x+6] &&
m.image[y+1][x+11] &&
m.image[y+1][x+12] &&
m.image[y+1][x+17] &&
m.image[y+1][x+18] &&
m.image[y+1][x+19] &&
m.image[y+2][x+1] &&
m.image[y+2][x+4] &&
m.image[y+2][x+7] &&
m.image[y+2][x+10] &&
m.image[y+2][x+13] &&
m.image[y+2][x+16] {
monsters++
}
}
}
return monsters
}
func (m *message) countRoughness() int {
monsters := m.seaMonsters() * 15
total := 0
for y := 0; y < len(m.image); y++ {
for x := 0; x < len(m.image[0]); x++ {
if m.image[y][x] {
total++
}
}
}
return total - monsters
}
// PartOne Find the product of the ids of the four corners
func PartOne() string {
m := message{}
if err := m.load("twenty/input.txt"); err != nil {
return err.Error()
}
return fmt.Sprintf("The product of the four corners is %d", m.cornerProduct())
}
// PartTwo How many # are not seamonsters.
func PartTwo() string {
m := message{}
if err := m.load("twenty/input.txt"); err != nil {
return err.Error()
}
m.layout()
m.combineTiles()
m.alignMonsters()
return fmt.Sprintf("There are %d # that are not seamonsters", m.countRoughness())
}

75
twenty/day_twenty_test.go Normal file
View File

@@ -0,0 +1,75 @@
package twenty
import "testing"
func Test_tile_load(t *testing.T) {
m := message{}
m.load("sample.txt")
if len(m.tiles) != 9 {
t.Logf("Expected 9 tiles to be loaded, got %d", len(m.tiles))
t.FailNow()
}
if len(m.grid) != 3 && len(m.grid[0]) != 3 {
t.Logf("Expected a 3x3 grid, got %dx%d", len(m.grid), len(m.grid[0]))
t.FailNow()
}
}
func Test_arrange_tile(t *testing.T) {
m := message{}
m.load("sample.txt")
result := m.cornerProduct()
if result != 20899048083289 {
t.Logf("Expected a result of 20899048083289, got %d", result)
t.FailNow()
}
}
func Test_arrange_input(t *testing.T) {
m := message{}
m.load("input.txt")
result := m.cornerProduct()
if result == -1 {
t.Logf("Expected a result other than -1, got %d", result)
t.FailNow()
}
}
func Test_count_monsters(t *testing.T) {
m := message{}
m.load("sample.txt")
grid := m.layout()
if !grid {
t.Log("Expected a grid")
t.FailNow()
}
m.combineTiles()
m.alignMonsters()
result := m.seaMonsters()
if result != 2 {
t.Logf("Expected 2 monsters, found %d", result)
t.FailNow()
}
}
func Test_count_roughness(t *testing.T) {
m := message{}
m.load("sample.txt")
m.layout()
m.combineTiles()
m.alignMonsters()
result := m.countRoughness()
if result != 273 {
t.Logf("Expected 273 # not part of a monster, found %d", result)
t.FailNow()
}
}

1727
twenty/input.txt Normal file

File diff suppressed because it is too large Load Diff

3
twenty/monster.txt Normal file
View File

@@ -0,0 +1,3 @@
#
# ## ## ###
# # # # # #

107
twenty/sample.txt Normal file
View File

@@ -0,0 +1,107 @@
Tile 2311:
..##.#..#.
##..#.....
#...##..#.
####.#...#
##.##.###.
##...#.###
.#.#.#..##
..#....#..
###...#.#.
..###..###
Tile 1951:
#.##...##.
#.####...#
.....#..##
#...######
.##.#....#
.###.#####
###.##.##.
.###....#.
..#.#..#.#
#...##.#..
Tile 1171:
####...##.
#..##.#..#
##.#..#.#.
.###.####.
..###.####
.##....##.
.#...####.
#.##.####.
####..#...
.....##...
Tile 1427:
###.##.#..
.#..#.##..
.#.##.#..#
#.#.#.##.#
....#...##
...##..##.
...#.#####
.#.####.#.
..#..###.#
..##.#..#.
Tile 1489:
##.#.#....
..##...#..
.##..##...
..#...#...
#####...#.
#..#.#.#.#
...#.#.#..
##.#...##.
..##.##.##
###.##.#..
Tile 2473:
#....####.
#..#.##...
#.##..#...
######.#.#
.#...#.#.#
.#########
.###.#..#.
########.#
##...##.#.
..###.#.#.
Tile 2971:
..#.#....#
#...###...
#.#.###...
##.##..#..
.#####..##
.#..####.#
#..#.#..#.
..####.###
..#.#.###.
...#.#.#.#
Tile 2729:
...#.#.#.#
####.#....
..#.#.....
....#..#.#
.##..##.#.
.#.####...
####.#.#..
##.####...
##..#.##..
#.##...##.
Tile 3079:
#.#.#####.
.#..######
..#.......
######....
####.#..#.
.#...#.##.
#.#####.##
..#.###...
..#.......
..#.###...