generated from Jadarma/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathY2021D09.kt
87 lines (75 loc) · 3.14 KB
/
Y2021D09.kt
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
package aockt.y2021
import io.github.jadarma.aockt.core.Solution
object Y2021D09 : Solution {
/** Represents a discrete point in 2D space. */
private data class Point(val x: Int, val y: Int)
/** Represents the scanning of the lava tubes inside the cave system. */
private class LavaTubesMap(val width: Int, val height: Int, private val data: IntArray) {
init {
require(data.size == width * height) { "The map size does not match given data." }
require(data.all { it in 0..9 }) { "Map data contains invalid readings." }
}
operator fun get(point: Point) = data[point.x * width + point.y]
/** Return all the local minima from the map. */
fun lowPoints(): List<Point> = buildList {
for (x in 0 until height) {
for (y in 0 until width) {
val point = Point(x, y)
val value = this@LavaTubesMap[point]
if (adjacentTo(point).all { get(it) > value }) add(point)
}
}
}
/** Return a list of all basins, represented as the set of points that it contains. */
fun basins(): List<Set<Point>> {
val visited = mutableSetOf<Point>()
return lowPoints().map { searchBasin(it, visited) }
}
/** Return all the points orthogonally adjacent to the [point], adjusting for map borders. */
private fun adjacentTo(point: Point): List<Point> = buildList(8) {
with(point) {
if (x > 0) add(Point(x - 1, y))
if (x < height - 1) add(Point(x + 1, y))
if (y > 0) add(Point(x, y - 1))
if (y < width - 1) add(Point(x, y + 1))
}
}
/** Recursively build a basin starting [from] a point, and keeping track of [visited] points. */
private fun searchBasin(from: Point, visited: MutableSet<Point>): Set<Point> = buildSet {
add(from)
adjacentTo(from)
.filter { this@LavaTubesMap[it] < 9 }
.filter { it !in visited }
.forEach {
add(it)
visited.add(it)
addAll(searchBasin(from = it, visited))
}
}
}
/** Parse the [input] and return the [LavaTubesMap] associated to the cave system reading. */
private fun parseInput(input: String): LavaTubesMap {
var width = -1
var height = 0
val data = input
.lineSequence()
.onEach {
height++
if (width == -1) width = it.length else require(it.length == width)
}
.flatMap { line -> line.map { it.digitToInt() } }
.toList()
.toIntArray()
return LavaTubesMap(width, height, data)
}
override fun partOne(input: String) =
parseInput(input).run {
lowPoints().sumOf { this[it] + 1 }
}
override fun partTwo(input: String) =
parseInput(input)
.basins()
.sortedByDescending { it.size }
.take(3)
.fold(1) { acc, basin -> acc * basin.size }
}