Files
advent-of-code-go/2021/day04/main.go
T
2021-12-04 00:42:09 -05:00

176 lines
3.4 KiB
Go

package main
import (
_ "embed"
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/cast"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
nums, boards := parseInput(input)
for _, n := range nums {
for _, b := range boards {
didWin := b.PickNum(n)
if didWin {
// multiply score of winning board by number that was just called
return b.Score() * n
}
}
}
panic("a board should've won and returned from the loop")
}
func part2(input string) int {
nums, boards := parseInput(input)
lastWinningScore := -1
alreadyWon := map[int]bool{}
for _, n := range nums {
for bi, b := range boards {
if alreadyWon[bi] {
continue
}
didWin := b.PickNum(n)
if didWin {
// WHICH BOARD WINS LAST
lastWinningScore = b.Score() * n
// mark board as already won
alreadyWon[bi] = true
}
}
}
return lastWinningScore
}
// BoardState maintains a parsed board and a boolean matrix of cells that have
// been picked/marked
type BoardState struct {
board [][]int
picked [][]bool
}
func NewBoardState(board [][]int) BoardState {
picked := make([][]bool, len(board))
for i := range picked {
picked[i] = make([]bool, len(board[0]))
}
return BoardState{
board: board,
picked: picked,
}
}
func (b *BoardState) PickNum(num int) bool {
for r, rows := range b.board {
for c, v := range rows {
if v == num {
b.picked[r][c] = true
}
}
}
// is this fast enough to do on every "cycle"?
// guess so. probably a constant time way to do this but oh well
for i := 0; i < len(b.board); i++ {
isFullRow, isFullCol := true, true
// board is square so this works fine, otherwise would need another pair of nested loops
for j := 0; j < len(b.board); j++ {
// check row at index i
if !b.picked[i][j] {
isFullRow = false
}
// check col at index j
if !b.picked[j][i] {
isFullCol = false
}
}
if isFullRow || isFullCol {
// returns true if is winning board
return true
}
}
// false for incomplete board
return false
}
func (b *BoardState) Score() int {
var score int
for r, rows := range b.board {
for c, v := range rows {
// adds up all the non-picked/marked cells
if !b.picked[r][c] {
score += v
}
}
}
return score
}
func parseInput(input string) (nums []int, boards []BoardState) {
lines := strings.Split(input, "\n\n")
for _, v := range strings.Split(lines[0], ",") {
nums = append(nums, cast.ToInt(v))
}
for _, grid := range lines[1:] {
b := [][]int{}
for _, line := range strings.Split(grid, "\n") {
line = strings.ReplaceAll(line, " ", " ")
for line[0] == ' ' {
line = line[1:]
}
parts := strings.Split(line, " ")
row := []int{}
for _, p := range parts {
row = append(row, cast.ToInt(p))
}
b = append(b, row)
}
boards = append(boards, NewBoardState(b))
}
return nums, boards
}