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:
Chris Hodges 2023-12-24 14:50:09 +01:00
parent 6b904bb42c
commit 0acad0121c
3 changed files with 208 additions and 1 deletions

View File

@ -47,7 +47,7 @@ fun primeFactors(n: Long, bits: BitSet): List<Long> {
if (remainder == 1L) {
return trivialFactors
}
val factors = ArrayList<Long>(trivialFactors)
val factors = ArrayList(trivialFactors)
if (!remainder.toBigInteger().isProbablePrime(20)) {
val divisor = rhoBrent(remainder)
factors.addAll(primeFactors(divisor, bits))

View File

@ -9,6 +9,17 @@ fun Long.lcm(b: Long): Long = this / gcd(b) * b
fun Iterable<Int>.lcm(): Int = reduce(Int::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.
*/

196
src/aoc2023/Day24.kt Normal file
View 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()
}