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)) } } } } } }