AdventOfCode2021/04/main.go

186 lines
3.5 KiB
Go

package main
import (
"bufio"
"fmt"
"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)
}
func makeScanner(test bool) *bufio.Scanner {
var f *os.File
if test {
f, _ = os.Open("inputs/testinput")
} else {
f, _ = os.Open("inputs/input")
}
reader := bufio.NewReader(f)
return bufio.NewScanner(reader)
}
func mustAtoi(line string) int {
i, err := strconv.Atoi(line)
if err != nil {
panic(err)
}
return i
}
func convertDraws(d []string) []int {
r := make([]int, len(d))
for i := range d {
r[i] = mustAtoi(d[i])
}
return r
}
func contains(i int, a []int) bool {
for _, j := range a {
if i == j {
return true
}
}
return false
}
func bingo(set map[int]struct{}, drawnNumbers []int) bool {
hits := 0
for _, i := range drawnNumbers {
if _, ok := set[i]; ok {
hits++
}
}
if hits == 5 {
return true
}
return false
}
// a bingo card like this:
// 60 79 46 9 58
// 97 81 6 94 84
// 38 40 17 61 29
// 11 28 0 91 15
// 24 77 34 59 36
// turns into this:
// rows: [][60 79 46 9 58][97 81 6 94 84]...
// columns: [][60 97 38 11 24][79 81 40 28 77]...
// so that checking all possible hits is easier
// we use maps and not arrays to also make the lookups quicker
type bingoCard struct {
allNumbers []int
rows []map[int]struct{}
columns []map[int]struct{}
winner bool
}
func readBingoCard(scanner *bufio.Scanner) bingoCard {
card := bingoCard{
allNumbers: []int{},
rows: []map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}, 4: {}},
columns: []map[int]struct{}{0: {}, 1: {}, 2: {}, 3: {}, 4: {}},
}
for i := 0; i < 5; i++ {
scanner.Scan()
row := strings.Split(scanner.Text(), " ")
var col int
for _, p := range row {
if p != "" {
num := mustAtoi(p)
card.allNumbers = append(card.allNumbers, num)
card.rows[i][num] = struct{}{}
card.columns[col][num] = struct{}{}
col++
}
}
}
return card
}
func checkBoard(currentNumbers []int, card bingoCard) bool {
for _, row := range card.rows {
if bingo(row, currentNumbers) {
return true
}
}
for _, col := range card.columns {
if bingo(col, currentNumbers) {
return true
}
}
return false
}
func scoreBoard(currentNumbers []int, card bingoCard) int {
totalUncalled := 0
for _, i := range card.allNumbers {
if !contains(i, currentNumbers) {
totalUncalled += i
}
}
return totalUncalled * currentNumbers[len(currentNumbers)-1]
}
func partOne() {
scanner := makeScanner(false)
scanner.Scan()
draws := convertDraws(strings.Split(scanner.Text(), ","))
cards := []bingoCard{}
for scanner.Scan() {
cards = append(cards, readBingoCard(scanner))
}
for i := range draws {
for _, card := range cards {
winner := checkBoard(draws[0:i], card)
if winner {
fmt.Println(scoreBoard(draws[0:i], card))
return
}
}
}
}
func partTwo() {
scanner := makeScanner(false)
scanner.Scan()
draws := convertDraws(strings.Split(scanner.Text(), ","))
cards := []bingoCard{}
for scanner.Scan() {
cards = append(cards, readBingoCard(scanner))
}
winnerCount := 0
for i := range draws {
for j, card := range cards {
if !card.winner {
winner := checkBoard(draws[0:i], card)
if winner {
cards[j].winner = true
winnerCount++
if winnerCount == len(cards) {
// that was the last one!
fmt.Println(scoreBoard(draws[0:i], card))
}
}
}
}
}
}