AdventOfCode2020/20/main.go

245 lines
6.7 KiB
Go
Raw Permalink Normal View History

2020-12-21 20:03:39 +00:00
package main
import (
"bufio"
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type tile struct {
id int
neighbors map[int]int // key matches the edge key, so we can skip existing matches if we want
edges map[int]string
image map[int]string
}
// edges are always defined clockwise starting from 0:north
// as long as there's only ever one match for an edge, I don't _think_ I need to keep track of piece orientation...?
func parseTile(header string, body []string) tile {
id, _ := strconv.Atoi(strings.Trim(header, "Tile :"))
t := tile{
id: id,
neighbors: make(map[int]int, 4),
edges: make(map[int]string, 4),
image: make(map[int]string, 8),
}
// make edges and extract picture data...
left := []byte{}
right := []byte{}
for i := range body {
left = append(left, body[i][0])
right = append(right, body[i][9])
// image has edges removed; still want 0-indexed for sanity later!
if i != 0 && i != 9 {
t.image[i-1] = body[i-1][1:9]
}
}
t.edges[0] = body[0]
t.edges[1] = string(right)
t.edges[2] = body[9]
t.edges[3] = string(left)
return t
}
// return is match, inverted
func testEdge(one, two string) (bool, bool) {
if one == two {
return true, false
}
// go doesn't have a built-in "string reversal" method, so we just check it the hard way
// 9 is our magic number for a 10-character string, which we know they all are.
// This might not be 100% sound for unicode (byte != rune), but our string is ASCII so it's... fine.
for i := range one {
if one[i] != two[9-i] {
return false, false
}
}
return true, true
}
// this is wasteful in that it happily checks edges that we've already determined neighbors for,
// but it's performant enough that I don't really care to fix it
func findPartners(id int, tiles map[int]tile) map[int]tile {
currentTile := tiles[id]
for tile := range tiles {
if tile != id {
for edgeID := range tiles[tile].edges {
for i := range currentTile.edges {
if ok, _ := testEdge(tiles[tile].edges[edgeID], currentTile.edges[i]); ok {
tiles[tile].neighbors[edgeID] = tiles[id].id
tiles[id].neighbors[i] = tiles[tile].id
}
}
}
}
}
return tiles
}
// Starting from a corner, we can "walk" through the image by:
// stepping to the next tile in a known direction
// checking where _that_ tile thinks we came from
// then re-orienting ourselves and stepping again.
// For example, if we take a corner and go to its 0 neighbor,
// and that neighbor thinks our previous tile was 3,
// a "straight line" along the edge would be 1. Repeat as necessary until you hit an edge.
// Once we hit an edge, return to the start of the row,
// step to its 90° neighbor, then walk parallel to the initial edge.
// Repeat until we reach the far corner.
// Additionally, we need to "line up" the edges, so there's some alignment checking that has to happen.
func mergeTiles(corner int, tiles map[int]tile) []string {
// just to make the counting a little easier
sideLength := int(math.Sqrt(float64(len(tiles))))
result := make([]string, sideLength*8)
headOfRow := tiles[corner]
currentRowDirection := 0
nextRowDirection := 0
// what an absurd way to "pick" between two keys in a two-key map
for i := range headOfRow.neighbors {
if currentRowDirection == 0 {
currentRowDirection = i
} else if nextRowDirection == 0 {
nextRowDirection = i
}
}
currentTile := tiles[corner]
image := imageFromEdge(currentTile, 0)
for row, s := range image {
result[row] = result[row] + s
}
for i := 0; i < sideLength; i = i + 1 {
nextID := currentTile.neighbors[currentRowDirection]
for e, id := range tiles[nextID].neighbors {
if id == currentTile.id {
_, invert := testEdge(currentTile.edges[currentRowDirection], tiles[nextID].edges[e])
image := imageFromEdge(tiles[nextID], (e+1)%4)
if !invert {
for row, s := range image {
result[row] = result[row] + s
}
} else {
for row, s := range image {
result[7-row] = result[7-row] + s
}
}
currentRowDirection = (e + 2) % 4 // flip 180 around the compass
}
}
currentTile = tiles[nextID]
}
return result
}
// returns the image "rotated" so that the provided edge is on "top" (the 0 position)
func imageFromEdge(t tile, edge int) map[int]string {
if edge == 1 {
newImage := make(map[int]string, 8)
for i := 0; i < 8; i = i + 1 {
b := []byte{t.image[0][7-i], t.image[1][7-i], t.image[2][7-i], t.image[3][7-i], t.image[4][7-i], t.image[5][7-i], t.image[6][7-i], t.image[7][7-i]}
newImage[i] = string(b)
}
return newImage
}
if edge == 2 {
newImage := make(map[int]string, 8)
for i := 0; i < 8; i = i + 1 {
b := []byte{t.image[7-i][7], t.image[7-i][6], t.image[7-i][5], t.image[7-i][4], t.image[7-i][3], t.image[7-i][2], t.image[7-i][1], t.image[7-i][0]}
newImage[i] = string(b)
}
return newImage
}
if edge == 3 {
newImage := make(map[int]string, 8)
for i := 0; i < 8; i = i + 1 {
b := []byte{t.image[7][i], t.image[6][i], t.image[5][i], t.image[4][i], t.image[3][i], t.image[2][i], t.image[1][i], t.image[0][i]}
newImage[i] = string(b)
}
return newImage
}
// edge == 0 does nothing
return t.image
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
buffer := []string{}
tiles := make(map[int]tile)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
buffer = []string{}
} else {
buffer = append(buffer, line)
}
}
// catch that last tile
// weirdly, I'd think the trailing newline in the input would have done this for us
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
corners := 1
for id := range tiles {
tiles = findPartners(id, tiles)
if len(tiles[id].neighbors) == 2 {
corners = corners * id
}
}
fmt.Println(corners)
}
func partTwo() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
buffer := []string{}
tiles := make(map[int]tile)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
buffer = []string{}
} else {
buffer = append(buffer, line)
}
}
// catch that last tile
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
corner := 0
for id := range tiles {
tiles = findPartners(id, tiles)
// grab a corner to start the merge from
if len(tiles[id].neighbors) == 2 && corner == 0 {
corner = id
}
}
for _, line := range mergeTiles(corner, tiles) {
fmt.Println(line)
}
}