generated from Jadarma/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathY2021D05.kt
83 lines (67 loc) · 3.24 KB
/
Y2021D05.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
package aockt.y2021
import io.github.jadarma.aockt.core.Solution
import kotlin.math.sign
object Y2021D05 : Solution {
/** Represents a discrete point in 2D space. */
private data class Point(val x: Int, val y: Int) {
operator fun plus(delta: Delta): Point = Point(x + delta.dx, y + delta.dy)
}
/** Represents a direction vector in 2D space. */
private data class Delta(val dx: Int, val dy: Int)
/** Represents a line in 2D space. */
private data class Line(val p1: Point, val p2: Point) {
init {
require(p1 != p2) { "A line must connect two distinct points." }
}
/** Get the delta step between the two points, or the direction vector of a unitary step. */
val pointDelta: Delta get() = Delta((p2.x - p1.x).sign, (p2.y - p1.y).sign)
}
/** Holds a square map with side of give [size] of an ocean floor, used to track vents. */
private class OceanFloor(val size: Int) {
private val data = ByteArray(size * size) { 0 }
private var _intersections: Int = 0
operator fun get(point: Point): Boolean = data[point.x * size + point.y] > 0
operator fun set(point: Point, value: Boolean) {
val current = data[point.x * size + point.y]
data[point.x * size + point.y] = when (value) {
true -> {
if (current == 1.toByte()) _intersections++
if (current < Byte.MAX_VALUE) current.inc() else 0.toByte()
}
false -> {
if (current == 2.toByte()) _intersections--
maxOf(current.dec(), 0.toByte())
}
}
}
/** The number of points where at least two lines intersect, making it especially dangerous. */
fun intersections(): Int = _intersections
/** Mark the [line] as dangerous. */
fun setLine(line: Line) {
var pencil = line.p1
val delta = line.pointDelta
this[pencil] = true
while (pencil != line.p2) {
pencil += delta
this[pencil] = true
}
}
}
/** The format of each line of input. */
private val lineRegex = Regex("""^(\d+),(\d+) -> (\d+),(\d+)$""")
/** Parse the input and return a sequence of [Line]s defined in it. */
private fun parseInput(input: String): Sequence<Line> = input
.lineSequence()
.map { lineRegex.matchEntire(it)?.groupValues ?: throw IllegalArgumentException() }
.map { group -> group.drop(1).map { it.toIntOrNull() ?: throw IllegalArgumentException() } }
.map { (x1, y1, x2, y2) -> Line(Point(x1, y1), Point(x2, y2)) }
// Might be nice to determine the map bounds manually instead of defaulting to 1000x1000, but that would
// require either parsing the input twice, or holding it all in memory.
override fun partOne(input: String): Any = parseInput(input)
.filter { with(it.pointDelta) { dx == 0 || dy == 0 } }
.fold(OceanFloor(1000)) { acc, line -> acc.apply { setLine(line) } }
.intersections()
override fun partTwo(input: String): Any = parseInput(input)
.fold(OceanFloor(1000)) { acc, line -> acc.apply { setLine(line) } }
.intersections()
}