aboutsummaryrefslogtreecommitdiff
path: root/aoc-2021-kotlin/src/Day04.kt
blob: 96cdf3b12c55c7ea092958f2d1816a4c071c21aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
object Day04 {
    private class Bingo(private val revealQueue: ArrayDeque<Int>, private val boards: List<Board>) {
        companion object {
            fun fromString(input: String): Bingo {
                val sections = input.trim().split("(\\r?\\n){2}".toRegex())

                val revealQueueString = sections.first()
                val revealQueue = ArrayDeque(revealQueueString.split(",").map(String::toInt))

                val boards = sections.drop(1).map { Board.fromMatrixString(it) }

                return Bingo(revealQueue, boards)
            }
        }

        fun getResults() = sequence {
            while (revealQueue.isNotEmpty() && boards.any { !it.didWin }) {
                val number = revealQueue.removeFirst()

                for (board in boards.filter { !it.didWin }) {
                    board.reveal(number)

                    if (board.didWin) {
                        yield(board.sumOfUnmarked * number)
                    }
                }
            }
        }

        private class Board(private var rows: List<List<Square>>) {
            companion object {
                fun fromMatrixString(matrixString: String): Board = Board(
                    matrixString.trim().split("\\r?\\n".toRegex()).map { rowString ->
                        rowString.trim().split("\\s+".toRegex()).map { squareString ->
                            Square.Unmarked(squareString.toInt())
                        }
                    }
                )

                private const val SIZE = 5

                private val ROWS = (0 until SIZE).map { row ->
                    (0 until SIZE).map { square ->
                        Pair(row, square)
                    }
                }

                private val COLUMNS = (0 until SIZE).map { column ->
                    (0 until SIZE).map { square ->
                        Pair(square, column)
                    }
                }

                private val WINNING_CONFIGURATIONS = ROWS + COLUMNS
            }

            fun reveal(number: Int) {
                rows = rows.map { row ->
                    row.map { square ->
                        if (square is Square.Unmarked && square.number == number) Square.Marked else square
                    }
                }
            }

            val didWin: Boolean
                get() = WINNING_CONFIGURATIONS.any { configuration ->
                    configuration.all { (row, column) -> rows[row][column] is Square.Marked }
                }

            val sumOfUnmarked: Int
                get() = rows.fold(0) { rowAcc, row ->
                    rowAcc + row.fold(0) { squareAcc, square ->
                        squareAcc + if (square is Square.Unmarked) square.number else 0
                    }
                }

            sealed class Square {
                object Marked : Square()
                class Unmarked(val number: Int) : Square()
            }
        }
    }

    fun bothParts(input: String) = Bingo.fromString(input).getResults().let { it.first() to it.last() }
}

fun main() {
    val testInput = readInputAsString("Day04_test")
    val testOutput = Day04.bothParts(testInput)
    check(testOutput.first == 4512)
    check(testOutput.second == 1924)

    val input = readInputAsString("Day04")
    val output = Day04.bothParts(input)
    println(output.first)
    println(output.second)
}