diff --git a/2021/05/main.kt b/2021/05/main.kt index a71535b..abbed9f 100644 --- a/2021/05/main.kt +++ b/2021/05/main.kt @@ -1,38 +1,62 @@ import java.io.File +import kotlin.math.abs import kotlin.math.max import kotlin.math.min +import kotlin.system.measureTimeMillis fun main() { val lineRegex = Regex("^(\\d+),(\\d+) -> (\\d+),(\\d+)$") val lines = File("input/1.txt").readLines() .mapNotNull(lineRegex::matchEntire) .map { it.groupValues.takeLast(4).map(String::toInt) } - .map { Line.of(it[0], it[1], it[2], it[3]) } + .map { Line(it[0], it[1], it[2], it[3]) } - val straightLines = lines.filter(Line::isStraight) - val straightLineFloor = Floor(straightLines) - - println("Points where at least 2 straight lines overlap: ${straightLineFloor.countPointsWithOverlap(2)}") + println("(Took ${measureTimeMillis { part1(lines) }} ms)") + println("(Took ${measureTimeMillis { part2(lines) }} ms)") } -@Suppress("ProtectedInFinal") -data class Line protected constructor(val x1: Int, val y1: Int, val x2: Int, val y2: Int) { - companion object { - fun of(x1: Int, y1: Int, x2: Int, y2: Int): Line { - return Line(min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)) - } +@Suppress("ProtectedInFinal", "MemberVisibilityCanBePrivate") +data class Line(val x1: Int, val y1: Int, val x2: Int, val y2: Int) { + val minX = min(x1, x2) + val minY = min(y1, y2) + val maxX = max(x1, x2) + val maxY = max(y1, y2) + + init { + require(isStraight || is45Degrees) { "Line must be straight or have a slope of 45 degrees!" } } val isStraight get() = x1 == x2 || y1 == y2 + val is45Degrees + get() = abs(x2 - x1) == abs(y2 - y1) + + val length = 1 + max(abs(x2 - x1), abs(y2 - y1)) + + private val slopeX = Integer.signum(x2 - x1) + private val slopeY = Integer.signum(y2 - y1) + fun contains(x: Int, y: Int): Boolean { - return if (x1 == x2) - x == x1 && y in y1..y2 - else if (y1 == y2) - y == y1 && x in x1..x2 - else - throw UnsupportedOperationException() + if (x1 == x2) { + return x == x1 && y in minY..maxY + } + + if (y1 == y2) { + return y == y1 && x in minX..maxX + } + + if (x !in minX..maxX || y !in minY..maxY) { + return false + } + + for (i in 0 until length) { + if (x == x1 + (slopeX * i) && y == y1 + (slopeY * i)) { + return true + } + } + + return false } } @@ -43,13 +67,22 @@ class Floor(private val lines: List<Line>) { private val maxY = lines.maxOf { max(it.y1, it.y2) } fun countPointsWithOverlap(minOverlap: Int): Int { - val xs = minX..maxX + val xs = minX..maxY val ys = minY..maxY - return xs.sumOf { x -> - ys.count { y -> - lines.count { it.contains(x, y) } >= minOverlap + val xy = xs.flatMap { x -> ys.map { y -> x to y } } + return xy.parallelStream().filter { (x, y) -> hasOverlapOf(x, y, minOverlap) }.count().toInt() + } + + private fun hasOverlapOf(x: Int, y: Int, minOverlap: Int): Boolean { + var count = 0 + + for (line in lines) { + if (line.contains(x, y) && ++count >= minOverlap) { + return true } } + + return false } @Suppress("unused") @@ -69,3 +102,14 @@ class Floor(private val lines: List<Line>) { } } } + +fun part1(lines: List<Line>) { + val straightLines = lines.filter(Line::isStraight).ifEmpty { return } + val straightLineFloor = Floor(straightLines) + println("Points where at least 2 straight lines overlap: ${straightLineFloor.countPointsWithOverlap(2)}") +} + +fun part2(lines: List<Line>) { + val floor = Floor(lines) + println("Points where at least 2 lines overlap: ${floor.countPointsWithOverlap(2)}") +}