|
| 1 | +import java.util.* |
| 2 | + |
| 3 | +fun main() { |
| 4 | + |
| 5 | + data class Point( |
| 6 | + val row: Int, |
| 7 | + val column: Int, |
| 8 | + val value: Int |
| 9 | + ) |
| 10 | + |
| 11 | + fun List<String>.parse() = map { row -> row.map { it.digitToInt() } } |
| 12 | + |
| 13 | + fun List<List<Int>>.getOrMaxInt( |
| 14 | + rowIndex: Int, |
| 15 | + columnIndex: Int |
| 16 | + ) = getOrElse(rowIndex) { emptyList() }.getOrElse(columnIndex) { Int.MAX_VALUE } |
| 17 | + |
| 18 | + fun List<List<Int>>.getOrMinInt( |
| 19 | + rowIndex: Int, |
| 20 | + columnIndex: Int |
| 21 | + ) = getOrElse(rowIndex) { emptyList() }.getOrElse(columnIndex) { Int.MIN_VALUE }.takeUnless { it == 9 } |
| 22 | + ?: Int.MIN_VALUE |
| 23 | + |
| 24 | + fun List<List<Int>>.findLowestPoints() = mapIndexed { rowIndex, row -> |
| 25 | + row.mapIndexedNotNull { columnIndex, value -> |
| 26 | + if (value < getOrMaxInt(rowIndex - 1, columnIndex) |
| 27 | + && value < getOrMaxInt(rowIndex + 1, columnIndex) |
| 28 | + && value < getOrMaxInt(rowIndex, columnIndex - 1) |
| 29 | + && value < getOrMaxInt(rowIndex, columnIndex + 1) |
| 30 | + ) { |
| 31 | + Point(rowIndex, columnIndex, value) |
| 32 | + } else null |
| 33 | + } |
| 34 | + }.flatten() |
| 35 | + |
| 36 | + fun List<List<Int>>.findBasin(lowestPoint: Point): List<Point> { |
| 37 | + val visited = Array(size) { BooleanArray(first().size) { false } } |
| 38 | + val basin = mutableListOf<Point>() |
| 39 | + |
| 40 | + val queue = LinkedList<Point>() |
| 41 | + queue.add(lowestPoint) |
| 42 | + basin.add(lowestPoint) |
| 43 | + visited[lowestPoint.row][lowestPoint.column] = true |
| 44 | + |
| 45 | + while (queue.isNotEmpty()) { |
| 46 | + val point = queue.removeFirst() |
| 47 | + |
| 48 | + listOf( |
| 49 | + Point(point.row - 1, point.column, getOrMinInt(point.row - 1, point.column)), |
| 50 | + Point(point.row + 1, point.column, getOrMinInt(point.row + 1, point.column)), |
| 51 | + Point(point.row, point.column - 1, getOrMinInt(point.row, point.column - 1)), |
| 52 | + Point(point.row, point.column + 1, getOrMinInt(point.row, point.column + 1)) |
| 53 | + ) |
| 54 | + .filter { point.value < getOrMinInt(it.row, it.column) && !visited[it.row][it.column] } |
| 55 | + .forEach { |
| 56 | + queue.add(it) |
| 57 | + basin.add(it) |
| 58 | + visited[it.row][it.column] = true |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + return basin |
| 63 | + } |
| 64 | + |
| 65 | + fun part1( |
| 66 | + input: List<String> |
| 67 | + ) = input.parse().let { array -> array.findLowestPoints().sumOf { it.value + 1 } } |
| 68 | + |
| 69 | + fun part2( |
| 70 | + input: List<String> |
| 71 | + ) = input.parse().let { array -> |
| 72 | + array.findLowestPoints() |
| 73 | + .map { array.findBasin(it).count() } |
| 74 | + .sortedDescending() |
| 75 | + .take(3) |
| 76 | + .reduce { acc, value -> acc * value } |
| 77 | + } |
| 78 | + |
| 79 | + val input = readInput("Day09") |
| 80 | + println(part1(input)) |
| 81 | + println(part2(input)) |
| 82 | +} |
0 commit comments