Coverage Summary for Class: Vector2D (day24p2)
Class |
Method, %
|
Line, %
|
Vector2D |
100%
(3/3)
|
100%
(3/3)
|
Vector2D$Companion |
100%
(1/1)
|
100%
(2/2)
|
Total |
100%
(4/4)
|
100%
(5/5)
|
package day24p2
import java.math.BigDecimal
import java.math.BigDecimal.ZERO
import java.math.MathContext.DECIMAL128
import java.math.RoundingMode.HALF_UP
fun calcCollisionStone(hailstones: List<Hailstone>): Hailstone = (-400L..400L).firstNotNullOf { vx ->
val (vy, px, py) = findCollisionPointXY { vy ->
hailstones.map { it.copy(vx = it.vx - vx, vy = it.vy - vy) }
} ?: return@firstNotNullOf null
val (vz, px2, pz) = findCollisionPointXY { vz ->
hailstones.map { it.copy(py = it.pz, vx = it.vx - vx, vy = it.vz - vz) }
}!!
require(px == px2)
Hailstone(px, py, pz, vx, vy, vz)
}
private fun findCollisionPointXY(hailstonesFn: (Long) -> List<Hailstone>): Triple<Long, Long, Long>? {
vy@ for (vy in -400L..400L) {
var collisionPoint: Vector2D? = null
val hailstones = hailstonesFn(vy)
for (i in 0 until hailstones.size - 1) {
for (j in i + 1 until hailstones.size) {
val hi = hailstones[i]
val hj = hailstones[j]
val intersection = hi.intersection(hj)
if (collisionPoint == null) {
collisionPoint = intersection
continue
}
if (intersection == null) {
if (hi.isParallelTo(hj)) continue
continue@vy
}
if (collisionPoint != intersection) {
continue@vy
}
}
}
requireNotNull(collisionPoint)
return Triple(vy, collisionPoint.x.toLong(), collisionPoint.y.toLong())
}
return null
}
fun List<Hailstone>.findNumberOfInteractions(testRange: ClosedRange<BigDecimal>): Long {
var count = 0L
for (i in 0 until size - 1) {
var localCount = 0L
for (j in i + 1 until size) {
val intersection = this[i].intersection(this[j], testRange)
if (intersection != null) {
count++
localCount++
}
}
}
return count
}
fun String.toHailstones(): List<Hailstone> = lineSequence().map { it.toHailstone() }.toList()
fun String.toHailstone(): Hailstone {
val nums = split(',', '@').map { it.trim().toLong() }
return Hailstone(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5])
}
data class Hailstone(val px: Long, val py: Long, val pz: Long, val vx: Long, val vy: Long, val vz: Long) {
fun isParallelTo(o: Hailstone): Boolean = area(
vx.toBD(),
vy.toBD(),
o.vx.toBD(),
o.vy.toBD()
).compareTo(ZERO) == 0
fun intersection(o: Hailstone): Vector2D? = intersection(px, py, vx, vy, o.px, o.py, o.vx, o.vy)
fun intersection(other: Hailstone, testRange: ClosedRange<BigDecimal>): Vector2D? =
intersection(other)?.let {
if (it.x in testRange && it.y in testRange) it
else null
}
companion object {
fun intersection(
p1A: Long,
p1B: Long,
v1A: Long,
v1B: Long,
p2A: Long,
p2B: Long,
v2A: Long,
v2B: Long
): Vector2D? = intersection(
p1A.toBD(),
p1B.toBD(),
v1A.toBD(),
v1B.toBD(),
p2A.toBD(),
p2B.toBD(),
v2A.toBD(),
v2B.toBD()
)
private fun intersection(
p1A: BigDecimal,
p1B: BigDecimal,
v1A: BigDecimal,
v1B: BigDecimal,
p2A: BigDecimal,
p2B: BigDecimal,
v2A: BigDecimal,
v2B: BigDecimal
): Vector2D? {
if (v2A.compareTo(ZERO) == 0) return null
val area = area(v1A, v1B, v2A, v2B)
if (area.compareTo(ZERO) == 0) return null
val t1 = ((p1B - p2B) * v2A - (p1A - p2A) * v2B).divide(area, DECIMAL128)
if (t1 < ZERO) return null
val t2 = (p1A - p2A + t1 * v1A).divide(v2A, DECIMAL128)
if (t2 < ZERO) return null
return Vector2D(p1A + t1 * v1A, p1B + t1 * v1B)
}
fun area(v1A: BigDecimal, v1B: BigDecimal, v2A: BigDecimal, v2B: BigDecimal): BigDecimal = v1A * v2B - v1B * v2A
}
}
fun Int.toBD() = toBigDecimal(DECIMAL128)
fun Long.toBD() = toBigDecimal(DECIMAL128)
fun String.toBD() = toBigDecimal(DECIMAL128)
data class Vector2D(val x: BigDecimal, val y: BigDecimal) {
fun eq(that: Vector2D): Boolean = this == that || this.round() == that.round()
private fun round(): Vector2D = copy(x = x.setScale(3, HALF_UP), y = y.setScale(3, HALF_UP))
companion object {
fun parse(expectedIntersection: String): Vector2D {
val (x, y) = expectedIntersection.trim('(', ')').split(',').map { it.trim().toBD() }
return Vector2D(x, y)
}
}
}