aboutsummaryrefslogtreecommitdiff
path: root/aoc-2021-kotlin/src/Day15.kt
blob: 6d0de5c78c477c685ac9e8125b0c0973eaeb452c (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import java.util.*

object Day15 {
    private class CustomPriorityQueue<T>(private val array: Array<T>, private val comparator: Comparator<T>) {
        init {
            array.sortWith(comparator)
        }

        private var i = 0

        private fun indexOfStartingFrom(elem: T, start: Int): Int {
            for (i in (start until array.size)) {
                if (array[i] == elem) {
                    return i
                }
            }

            return -1
        }

        fun isNotEmpty(): Boolean = i < array.size

        fun take(): T = array[i++]

        fun revalidate(elem: T) {
            val currentIndex = indexOfStartingFrom(elem, i)

            if (currentIndex == -1) {
                return
            }

            val newIndex = Arrays
                .binarySearch(array, i, currentIndex, elem, comparator)
                .let { if (it >= 0) it else -(it + 1) }

            System.arraycopy(array, newIndex, array, newIndex + 1, currentIndex - newIndex)

            array[newIndex] = elem
        }
    }

    private class Matrix(private val data: Array<IntArray>, private val size: Int) {
        companion object {
            fun fromInput(input: List<String>): Matrix {
                val data = input.map { line ->
                    line.map { it.digitToInt() }.toIntArray()
                }.toTypedArray()

                check(data.isNotEmpty()) { "No rows found!" }
                check(data.first().isNotEmpty()) { "No columns found!" }

                return Matrix(data, data.size)
            }
        }

        fun expand(): Matrix {
            val expandedData = Array(size * 5) { row ->
                IntArray(size * 5) { col ->
                    ((data[row % size][col % size] + row / size + col / size) - 1) % 9 + 1
                }
            }

            return Matrix(expandedData, size * 5)
        }

        private fun neighbours(pos: Pair<Int, Int>): Sequence<Pair<Int, Int>> {
            val offsets = sequenceOf(0 to 1, 1 to 0, 0 to -1, -1 to 0)

            return offsets
                .map { pos.first + it.first to pos.second + it.second }
                .filter { it.first in 0 until size && it.second in 0 until size }
        }

        fun dijkstra(): Int {
            val positions = (0 until size)
                .flatMap { row -> (0 until size).map { col -> row to col } }

            val distances =
                positions
                    .associateWith { Int.MAX_VALUE }
                    .toMutableMap()

            distances[0 to 0] = 0


            val queue = CustomPriorityQueue(positions.toTypedArray(), compareBy { distances[it]!! })

            while (queue.isNotEmpty()) {
                val u = queue.take()

                for (v in neighbours(u)) {
                    val newDist = distances[u]!! + data[v.first][v.second]
                    if (distances[v]!! > newDist) {
                        distances[v] = newDist
                        queue.revalidate(v)
                    }
                }
            }

            return distances[size - 1 to size - 1]!!
        }
    }

    fun part1(input: List<String>): Int = Matrix.fromInput(input).dijkstra()

    fun part2(input: List<String>): Int = Matrix.fromInput(input).expand().dijkstra()
}

fun main() {
    val testInput = readInputAsLines("Day15_test")
    check(Day15.part1(testInput) == 40)
    check(Day15.part2(testInput) == 315)

    val input = readInputAsLines("Day15")
    println(Day15.part1(input))
    println(Day15.part2(input))
}