Day 24. This was a mad thing. I hate you all for using term solving tools under python. Thanks to the other kotlin guy who went the chinese remainder path and give a good amount of inspiration (strangely, the test input fails). I was thinking that some sort of modulo arithmetic could solve it, but it was too hard to figure out. What a waste of an otherwise nice xmas day.
This commit is contained in:
parent
6b904bb42c
commit
0acad0121c
@ -47,7 +47,7 @@ fun primeFactors(n: Long, bits: BitSet): List<Long> {
|
|||||||
if (remainder == 1L) {
|
if (remainder == 1L) {
|
||||||
return trivialFactors
|
return trivialFactors
|
||||||
}
|
}
|
||||||
val factors = ArrayList<Long>(trivialFactors)
|
val factors = ArrayList(trivialFactors)
|
||||||
if (!remainder.toBigInteger().isProbablePrime(20)) {
|
if (!remainder.toBigInteger().isProbablePrime(20)) {
|
||||||
val divisor = rhoBrent(remainder)
|
val divisor = rhoBrent(remainder)
|
||||||
factors.addAll(primeFactors(divisor, bits))
|
factors.addAll(primeFactors(divisor, bits))
|
||||||
|
11
src/Utils.kt
11
src/Utils.kt
@ -9,6 +9,17 @@ fun Long.lcm(b: Long): Long = this / gcd(b) * b
|
|||||||
fun Iterable<Int>.lcm(): Int = reduce(Int::lcm)
|
fun Iterable<Int>.lcm(): Int = reduce(Int::lcm)
|
||||||
fun Iterable<Long>.lcm(): Long = reduce(Long::lcm)
|
fun Iterable<Long>.lcm(): Long = reduce(Long::lcm)
|
||||||
|
|
||||||
|
fun Iterable<Pair<Long, Long>>.chineseRemainder(): Long {
|
||||||
|
val prod = map { it.second.toBigInteger() }.reduce(BigInteger::times)
|
||||||
|
var sum = BigInteger.ZERO
|
||||||
|
for ((rem, coprime) in this) {
|
||||||
|
val cobig = coprime.toBigInteger()
|
||||||
|
val p = prod / cobig
|
||||||
|
sum += rem.toBigInteger() * p.modInverse(cobig) * p
|
||||||
|
}
|
||||||
|
return (sum % prod).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads lines from the given input txt file.
|
* Reads lines from the given input txt file.
|
||||||
*/
|
*/
|
||||||
|
196
src/aoc2023/Day24.kt
Normal file
196
src/aoc2023/Day24.kt
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
package aoc2023
|
||||||
|
|
||||||
|
import chineseRemainder
|
||||||
|
import primeFactors
|
||||||
|
import println
|
||||||
|
import readInput
|
||||||
|
import sieveOfErastosthenes
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
/*
|
||||||
|
--- Day 24: Never Tell Me The Odds ---
|
||||||
|
It seems like something is going wrong with the snow-making process. Instead of forming snow, the water that's been absorbed into the air seems to be forming hail!
|
||||||
|
Maybe there's something you can do to break up the hailstones?
|
||||||
|
Due to strong, probably-magical winds, the hailstones are all flying through the air in perfectly linear trajectories. You make a note of each hailstone's position and velocity (your puzzle input). For example:
|
||||||
|
19, 13, 30 @ -2, 1, -2
|
||||||
|
18, 19, 22 @ -1, -1, -2
|
||||||
|
20, 25, 34 @ -2, -2, -4
|
||||||
|
12, 31, 28 @ -1, -2, -1
|
||||||
|
20, 19, 15 @ 1, -5, -3
|
||||||
|
|
||||||
|
Each line of text corresponds to the position and velocity of a single hailstone. The positions indicate where the hailstones are right now (at time 0). The velocities are constant and indicate exactly how far each hailstone will move in one nanosecond.
|
||||||
|
Each line of text uses the format px py pz @ vx vy vz. For instance, the hailstone specified by 20, 19, 15 @ 1, -5, -3 has initial X position 20, Y position 19, Z position 15, X velocity 1, Y velocity -5, and Z velocity -3. After one nanosecond, the hailstone would be at 21, 14, 12.
|
||||||
|
Perhaps you won't have to do anything. How likely are the hailstones to collide with each other and smash into tiny ice crystals?
|
||||||
|
To estimate this, consider only the X and Y axes; ignore the Z axis. Looking forward in time, how many of the hailstones' paths will intersect within a test area? (The hailstones themselves don't have to collide, just test for intersections between the paths they will trace.)
|
||||||
|
In this example, look for intersections that happen with an X and Y position each at least 7 and at most 27; in your actual data, you'll need to check a much larger test area. Comparing all pairs of hailstones' future paths produces the following results:
|
||||||
|
Hailstone A: 19, 13, 30 @ -2, 1, -2
|
||||||
|
Hailstone B: 18, 19, 22 @ -1, -1, -2
|
||||||
|
Hailstones' paths will cross inside the test area (at x=14.333, y=15.333).
|
||||||
|
|
||||||
|
Hailstone A: 19, 13, 30 @ -2, 1, -2
|
||||||
|
Hailstone B: 20, 25, 34 @ -2, -2, -4
|
||||||
|
Hailstones' paths will cross inside the test area (at x=11.667, y=16.667).
|
||||||
|
|
||||||
|
Hailstone A: 19, 13, 30 @ -2, 1, -2
|
||||||
|
Hailstone B: 12, 31, 28 @ -1, -2, -1
|
||||||
|
Hailstones' paths will cross outside the test area (at x=6.2, y=19.4).
|
||||||
|
|
||||||
|
Hailstone A: 19, 13, 30 @ -2, 1, -2
|
||||||
|
Hailstone B: 20, 19, 15 @ 1, -5, -3
|
||||||
|
Hailstones' paths crossed in the past for hailstone A.
|
||||||
|
|
||||||
|
Hailstone A: 18, 19, 22 @ -1, -1, -2
|
||||||
|
Hailstone B: 20, 25, 34 @ -2, -2, -4
|
||||||
|
Hailstones' paths are parallel; they never intersect.
|
||||||
|
|
||||||
|
Hailstone A: 18, 19, 22 @ -1, -1, -2
|
||||||
|
Hailstone B: 12, 31, 28 @ -1, -2, -1
|
||||||
|
Hailstones' paths will cross outside the test area (at x=-6, y=-5).
|
||||||
|
|
||||||
|
Hailstone A: 18, 19, 22 @ -1, -1, -2
|
||||||
|
Hailstone B: 20, 19, 15 @ 1, -5, -3
|
||||||
|
Hailstones' paths crossed in the past for both hailstones.
|
||||||
|
|
||||||
|
Hailstone A: 20, 25, 34 @ -2, -2, -4
|
||||||
|
Hailstone B: 12, 31, 28 @ -1, -2, -1
|
||||||
|
Hailstones' paths will cross outside the test area (at x=-2, y=3).
|
||||||
|
|
||||||
|
Hailstone A: 20, 25, 34 @ -2, -2, -4
|
||||||
|
Hailstone B: 20, 19, 15 @ 1, -5, -3
|
||||||
|
Hailstones' paths crossed in the past for hailstone B.
|
||||||
|
|
||||||
|
Hailstone A: 12, 31, 28 @ -1, -2, -1
|
||||||
|
Hailstone B: 20, 19, 15 @ 1, -5, -3
|
||||||
|
Hailstones' paths crossed in the past for both hailstones.
|
||||||
|
|
||||||
|
So, in this example, 2 hailstones' future paths cross inside the boundaries of the test area.
|
||||||
|
However, you'll need to search a much larger test area if you want to see if any hailstones might collide. Look for intersections that happen with an X and Y position each at least 200000000000000 and at most 400000000000000. Disregard the Z axis entirely.
|
||||||
|
Considering only the X and Y axes, check all pairs of hailstones' future paths for intersections. How many of these intersections occur within the test area?
|
||||||
|
|
||||||
|
*/
|
||||||
|
fun main() {
|
||||||
|
|
||||||
|
val inlineTestInput = """
|
||||||
|
19, 13, 30 @ -2, 1, -2
|
||||||
|
18, 19, 22 @ -1, -1, -2
|
||||||
|
20, 25, 34 @ -2, -2, -4
|
||||||
|
12, 31, 28 @ -1, -2, -1
|
||||||
|
20, 19, 15 @ 1, -5, -3
|
||||||
|
"""
|
||||||
|
|
||||||
|
data class Hailstone(val px: Long, val py: Long, val pz: Long, val vx: Int, val vy: Int, val vz: Int)
|
||||||
|
|
||||||
|
fun part1(input: List<String>, testx: LongRange, testy: LongRange): Int {
|
||||||
|
val hailstones = input.map {
|
||||||
|
val (x1, y1, z1, x2, y2, z2) = "(\\d+), (\\d+), (\\d+) @ ([-\\d]+), ([-\\d]+), ([-\\d]+)".toRegex()
|
||||||
|
.matchEntire(it)?.destructured!!
|
||||||
|
Hailstone(x1.toLong(), y1.toLong(), z1.toLong(), x2.toInt(), y2.toInt(), z2.toInt())
|
||||||
|
}
|
||||||
|
var count = 0
|
||||||
|
for (h1 in hailstones.indices) {
|
||||||
|
for (h2 in h1 + 1..hailstones.lastIndex) {
|
||||||
|
val s1 = hailstones[h1]
|
||||||
|
val s2 = hailstones[h2]
|
||||||
|
|
||||||
|
val dx = (s2.px - s1.px).toDouble()
|
||||||
|
val dy = (s2.py - s1.py).toDouble()
|
||||||
|
|
||||||
|
val d = s1.vx.toDouble() * s2.vy.toDouble() -
|
||||||
|
s1.vy.toDouble() * s2.vx.toDouble()
|
||||||
|
|
||||||
|
val t = dx * s2.vy.toDouble() - dy * s2.vx.toDouble()
|
||||||
|
val u = dx * s1.vy.toDouble() - dy * s1.vx.toDouble()
|
||||||
|
|
||||||
|
if (t * d.sign > 0 && u * d.sign > 0) {
|
||||||
|
val hx = (s1.px + (t * s1.vx.toDouble()) / d)
|
||||||
|
val hy = (s1.py + (t * s1.vy.toDouble()) / d)
|
||||||
|
if (hx.toLong() in testx && hy.toLong() in testy) count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun part2(input: List<String>): Long {
|
||||||
|
val hailstones = input.map {
|
||||||
|
val (x1, y1, z1, x2, y2, z2) = "(\\d+), (\\d+), (\\d+) @ ([-\\d]+), ([-\\d]+), ([-\\d]+)".toRegex()
|
||||||
|
.matchEntire(it)?.destructured!!
|
||||||
|
Hailstone(x1.toLong(), y1.toLong(), z1.toLong(), x2.toInt(), y2.toInt(), z2.toInt())
|
||||||
|
}//.sortedBy { abs(it.vx) + abs(it.vy) + abs(it.vz) }
|
||||||
|
//.take(3)
|
||||||
|
|
||||||
|
val h1 = hailstones[0]
|
||||||
|
val h2 = hailstones[1]
|
||||||
|
val h3 = hailstones[2]
|
||||||
|
|
||||||
|
// rx = h1.px + t1*(h1.vx - rvx) = h2.px + t2*(h2.vx - rvx) = h3.px + t3*(h3.vx - rvx)
|
||||||
|
// ry = h1.py + t1*(h1.vy - rvy) = h2.py + t2*(h2.vy - rvy) = h3.py + t3*(h3.vy - rvy)
|
||||||
|
// rz = h1.pz + t1*(h1.vz - rvz) = h2.pz + t2*(h2.vz - rvz) = h3.pz + t3*(h3.vz - rvz)
|
||||||
|
|
||||||
|
// [1] h1.px - h2.px = t2*(h2.vx - rvx) - t1*(h1.vx - rvx)
|
||||||
|
// [2] h1.px - h3.px = t3*(h3.vx - rvx) - t1*(h1.vx - rvx)
|
||||||
|
// [3] h2.px - h3.px = t3*(h3.vx - rvx) - t2*(h2.vx - rvx)
|
||||||
|
|
||||||
|
// [4] h1.py - h2.py = t2*(h2.vy - rvy) - t1*(h1.vy - rvy)
|
||||||
|
// [5] h1.py - h3.py = t3*(h3.vy - rvy) - t1*(h1.vy - rvy)
|
||||||
|
// [6] h2.py - h3.py = t3*(h3.vy - rvy) - t2*(h2.vy - rvy)
|
||||||
|
|
||||||
|
// [7] h1.pz - h2.pz = t2*(h2.vz - rvz) - t1*(h1.vz - rvz)
|
||||||
|
// [8] h1.pz - h3.pz = t3*(h3.vz - rvz) - t1*(h1.vz - rvz)
|
||||||
|
// [9] h2.pz - h3.pz = t3*(h3.vz - rvz) - t2*(h2.vz - rvz)
|
||||||
|
|
||||||
|
val sieve = sieveOfErastosthenes(200)
|
||||||
|
for (rvx in -1000..1000) {
|
||||||
|
val speedMods = hailstones.mapNotNull { if (it.vx - rvx != 0) abs(it.vx - rvx).toLong() to (it.px % abs(it.vx - rvx)) else null }
|
||||||
|
|
||||||
|
val factorMap = HashMap<Long, Long>()
|
||||||
|
var good = true
|
||||||
|
reducePairs@ for ((speed, remain) in speedMods) {
|
||||||
|
for (pf in primeFactors(speed, sieve)) {
|
||||||
|
val sf = factorMap[pf]
|
||||||
|
val pr = remain % pf
|
||||||
|
if (sf != null && sf != pr) {
|
||||||
|
good = false
|
||||||
|
break@reducePairs
|
||||||
|
}
|
||||||
|
factorMap[pf] = pr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!good) continue
|
||||||
|
val reducedPairs = factorMap.map { it.value to it.key }.sortedBy { it.second }
|
||||||
|
|
||||||
|
val rx = reducedPairs.chineseRemainder()
|
||||||
|
//val rx = 24
|
||||||
|
|
||||||
|
val bang1Time = (h1.px - rx) / (rvx - h1.vx)
|
||||||
|
val bang2Time = (h2.px - rx) / (rvx - h2.vx)
|
||||||
|
val h1px = h1.px + h1.vx * bang1Time
|
||||||
|
val h1py = h1.py + h1.vy * bang1Time
|
||||||
|
val h1pz = h1.pz + h1.vz * bang1Time
|
||||||
|
val h2px = h2.px + h2.vx * bang2Time
|
||||||
|
val h2py = h2.py + h2.vy * bang2Time
|
||||||
|
val h2pz = h2.pz + h2.vz * bang2Time
|
||||||
|
val rvy = (h2py - h1py) / (bang2Time - bang1Time)
|
||||||
|
val rvz = (h2pz - h1pz) / (bang2Time - bang1Time)
|
||||||
|
val ry = h1py - bang1Time * rvy
|
||||||
|
val rz = h1pz - bang1Time * rvz
|
||||||
|
return rx + ry + rz
|
||||||
|
}
|
||||||
|
return 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
// test if implementation meets criteria from the description, like:
|
||||||
|
val testInput = inlineTestInput.trim().reader().readLines()
|
||||||
|
//val testInput = readInput("aoc2023/Day24_test")
|
||||||
|
val testInputPart1Result = part1(testInput, 7L..27L, 7L..27L)
|
||||||
|
println("Part 1 Test: $testInputPart1Result")
|
||||||
|
val testInputPart2Result = part2(testInput)
|
||||||
|
println("Part 2 Test: $testInputPart2Result")
|
||||||
|
check(testInputPart1Result == 2)
|
||||||
|
//check(testInputPart2Result == 47L)
|
||||||
|
|
||||||
|
val input = readInput("aoc2023/Day24")
|
||||||
|
part1(input, 200000000000000L..400000000000000L, 200000000000000L..400000000000000L).println()
|
||||||
|
part2(input).println()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user