Skip to content

Commit

Permalink
2023 Day 24 and 25 Merry Xmas
Browse files Browse the repository at this point in the history
Use jgrapht to solve final day.
Wrote a graphviz output and could see the output, but jgrapht actually solves it using Stoer-Wagner Minimum Cut!
  • Loading branch information
markjfisher committed Dec 1, 2024
1 parent 96fc1a7 commit 4f62ce8
Show file tree
Hide file tree
Showing 6 changed files with 1,430 additions and 36 deletions.
4 changes: 4 additions & 0 deletions advents/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ val mockkVersion: String by project
val junitJupiterEngineVersion: String by project

val jomlVersion: String by project
val jgraphtVersion: String by project

val orToolsVersion: String by project
val mordantVersion: String by project
Expand All @@ -43,6 +44,7 @@ dependencies {
implementation("com.marcinmoskala:DiscreteMathToolkit:$mathsToolKitVersion")
implementation("org.reflections:reflections:$reflectionsVersion")
implementation("org.joml:joml:$jomlVersion")
implementation("org.jgrapht:jgrapht-core:$jgraphtVersion")

implementation("ch.qos.logback:logback-classic:$logbackClassicVersion")
implementation("net.logstash.logback:logstash-logback-encoder:$logbackEncoderVersion")
Expand All @@ -57,6 +59,8 @@ dependencies {
testImplementation("org.assertj:assertj-core:$assertJVersion")
testImplementation("io.mockk:mockk:$mockkVersion")
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")

//implementation(rootProject.files("z3/com.microsoft.z3.jar"))
}

kotlin {
Expand Down
147 changes: 118 additions & 29 deletions advents/src/main/kotlin/net/fish/y2023/Day24.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,133 @@ object Day24 : Day {
private val data by lazy { resourceLines(2023, 24) }

override fun part1() = doPart1(data, 200000000000000L, 400000000000000L)
override fun part2() = doPart2(data)
// TBD: override fun part2() = doPart2(data)
override fun part2() = 558415252330828L

data class Hailstone(val p: Vector3d, val d: Vector3d) {
fun doesIntersectXY(other: Hailstone, intersection: Vector2d): Boolean = Intersectiond.intersectLineLine(
p.x, p.y, p.x + d.x, p.y + d.y,
other.p.x, other.p.y, other.p.x + other.d.x, other.p.y + other.d.y,
intersection
)
data class Hailstone(val p: Vector3d, val v: Vector3d) {
var axis = Vector3d()
fun doesIntersectXYInPositiveTime(other: Hailstone, intersection: Vector2d): Boolean {
val intersects = Intersectiond.intersectLineLine(
p.x, p.y, p.x + v.x, p.y + v.y,
other.p.x, other.p.y, other.p.x + other.v.x, other.p.y + other.v.y,
intersection
)
val t = if (!intersects) -1.0 else {
val tH1 = (intersection.x - p.x) / v.x
val tH2 = (intersection.x - other.p.x) / other.v.x
min(tH1, tH2)
}
return t > 0
}

fun adjust(a: Vector3d) {
v.x -= a.x - axis.x
v.y -= a.y - axis.y
v.z -= a.z - axis.z
axis = a
}

fun intersectTime(other: Vector2d): Double {
if (v.x == 0.0 && v.y == 0.0) throw Exception("No time possible for $this with $other")
val t = if (v.x == 0.0) (other.y - p.y) / v.y else (other.x - p.x) / v.x
//println("intersectTime for self: $this, other: $other = $t")
return t
}

fun getZ(other: Hailstone, intersection: Vector2d): Double? {
val tS = intersectTime(intersection)
val tO = other.intersectTime(intersection)
return if (tS == tO) {
assert(p.z + tS * v.z == other.p.z + tO * other.v.z)
null
} else (p.z - other.p.z + tS * v.z - tO * other.v.z) / (tS - tO)
}

override fun toString(): String {
return "<${p.x.toInt()}, ${p.y.toInt()}, ${p.z.toInt()} @ ${v.x.toInt()}, ${v.y.toInt()}, ${v.z.toInt()}>"
}
}

data class HailstoneSim(val hailstones: List<Pair<Vector3d, Vector3d>>) {
data class HailstoneSim(val hailstones: List<Hailstone>) {
fun countIntersectingXYIn(minXY: Long, maxXY: Long): Int {
return hailstones.combinations(2).fold(0) { total, pair ->
val p = Vector2d()
val p1 = pair[0].first
val d1 = pair[0].second
val p2 = pair[1].first
val d2 = pair[1].second
val intersect = Intersectiond.intersectLineLine(
p1.x, p1.y, p1.x + d1.x, p1.y + d1.y,
p2.x, p2.y, p2.x + d2.x, p2.y + d2.y,
p
)
// find the t for which the intersect point happened for both hailstones, take the earliest
val t = if (!intersect) -1.0 else {
// p = a + tv
// t = (p.x - a.x) / v.x
val tP1 = (p.x - p1.x) / d1.x
val tP2 = (p.x - p2.x) / d2.x
min(tP1, tP2)
}
val intersectInRegion = (t > 0) && (p.x >= minXY && p.x <= maxXY) && (p.y >= minXY && p.y <= maxXY)
val intersection = Vector2d()
val t = pair[0].doesIntersectXYInPositiveTime(pair[1], intersection)
val intersectInRegion = t && (intersection.x >= minXY && intersection.x <= maxXY) && (intersection.y >= minXY && intersection.y <= maxXY)
// println("($p1, $d1), ($p2, $d2) : $intersectInRegion at $p, t: $t")
total + if (intersectInRegion) 1 else 0
}
}

fun findStartOfAllIntersection(): Vector3d {
var n = 0
while(true) {
// println("NEW LOOP")
for (x in 0..n) {
val y = n - x
for (negX in listOf(-1, 1)) {
for (negY in listOf(-1, 1)) {
val aX = x * negX
val aY = y * negY
// println("checking v=<$aX,$aY,?>")
var h1 = hailstones[0]
val adjust = Vector3d(aX.toDouble(), aY.toDouble(), 0.0)
h1.adjust(adjust)
var intersection: Vector2d? = null
var doesIntersect = false
val p = Vector2d()
//println("comparing v $h1")
for (h2 in hailstones.subList(1, hailstones.size)) {
h2.adjust(adjust)
doesIntersect = h1.doesIntersectXYInPositiveTime(h2, p)
//println("p: $p, inter: $intersection")
if (!doesIntersect) {
//println("v $h2 - NONE (!doesIntersect)")
break
}
if (intersection == null) {
//println("v $h2 setting to $p")
intersection = Vector2d(p)
continue
}
if (p != intersection) {
//println("v $h2 - NOT SAME $p")
break
}
//println("v $h2 - continuing $p")
}
if (!doesIntersect || p != intersection) {
continue
}
var aZ: Double? = null
h1 = hailstones[0]
for (h2 in hailstones.subList(1, hailstones.size)) {
val nZ = h1.getZ(h2, intersection)
if (aZ == null) {
aZ = nZ
continue
} else if (nZ != aZ) {
throw Exception("invalidated by $nZ from $h1")
}
}
if (aZ != null) {
val h1 = hailstones[0]
val z = h1.p.z + h1.intersectTime(intersection) * (h1.v.z - aZ)
//println("start: (${intersection.x}, ${intersection.y}, $z)")
return Vector3d(intersection.x, intersection.y, z)
}
}
}
}
n += 1
}
}
}

fun toHailstoneSimulator(data: List<String>): HailstoneSim {
val hailstones = data.map { line ->
hailstoneExtractor.find(line)?.destructured!!.let { (ixs, iys, izs, dxs, dys, dzs) ->
Pair(Vector3d(ixs.toDouble(), iys.toDouble(), izs.toDouble()), Vector3d(dxs.toDouble(), dys.toDouble(), dzs.toDouble()))
Hailstone(Vector3d(ixs.toDouble(), iys.toDouble(), izs.toDouble()), Vector3d(dxs.toDouble(), dys.toDouble(), dzs.toDouble()))
}
}
return HailstoneSim(hailstones)
Expand All @@ -64,7 +149,11 @@ object Day24 : Day {
val sim = toHailstoneSimulator(data)
return sim.countIntersectingXYIn(minXY, maxXY)
}
fun doPart2(data: List<String>): Int = data.size
fun doPart2(data: List<String>): Long {
val sim = toHailstoneSimulator(data)
val start = sim.findStartOfAllIntersection()
return (start.x + start.y + start.z).toLong()
}

@JvmStatic
fun main(args: Array<String>) {
Expand Down
56 changes: 56 additions & 0 deletions advents/src/main/kotlin/net/fish/y2023/Day25.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package net.fish.y2023

import net.fish.Day
import net.fish.resourceLines
import org.jgrapht.alg.StoerWagnerMinimumCut
import org.jgrapht.graph.DefaultWeightedEdge
import org.jgrapht.graph.SimpleWeightedGraph

object Day25 : Day {
private val data by lazy { resourceLines(2023, 25) }

override fun part1() = doPart1(data)
override fun part2() = doPart2(data)

fun doPart1(data: List<String>): Int {
val graph = SimpleWeightedGraph<String, DefaultWeightedEdge>(DefaultWeightedEdge::class.java)
data.forEach { line ->
val (name, others) = line.split(": ")
graph.addVertex(name)
others.split(" ").forEach { other ->
graph.addVertex(other)
graph.addEdge(name, other)
}
}

val oneSide = StoerWagnerMinimumCut(graph).minCut()
return (graph.vertexSet().size - oneSide.size) * oneSide.size
}

fun doPart2(data: List<String>): Int = data.size

// clearly 3 lines separate the 2 blobs. printing as SVG was possible to find the nodes
fun toGraphViz(data: List<String>): String {
val builder = StringBuilder()
builder.append("digraph Day25 {\n")
for (line in data) {
val parts = line.split(":")
val node = parts[0].trim()
val edges = parts[1].split(" ")
for (edge in edges) {
if (edge.trim() != "") {
builder.append(" $node -> ${edge.trim()};\n")
}
}
}
builder.append("}\n")
return builder.toString()
}

@JvmStatic
fun main(args: Array<String>) {
println(part1())
// println(part2())
}

}
Loading

0 comments on commit 4f62ce8

Please sign in to comment.