aboutsummaryrefslogtreecommitdiff
path: root/src/Day04.kt
blob: 4f93a6753a7805025469dd1169501b4e17798f9b (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
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)
                }
            }
        }
    }

    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) { racc, row ->
                racc + row.fold(0) { sacc, square ->
                    sacc + if (square is Square.Unmarked) square.number else 0
                }
            }

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

fun main() {
    fun part1(input: String): Int = Bingo.fromString(input).getResults().first()

    fun part2(input: String): Int = Bingo.fromString(input).getResults().last()

    val testInput = readInputAsString("Day04_test")
    check(part1(testInput) == 4512)
    check(part2(testInput) == 1924)

    val input = readInputAsString("Day04")
    println(part1(input))
    println(part2(input))
}