Skip to content

Commit 4ff9701

Browse files
committed
Solution 2017-20 (Particle Swarm)
1 parent bae5703 commit 4ff9701

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package de.ronny_h.aoc.year2017.day20
2+
3+
import de.ronny_h.aoc.AdventOfCode
4+
import de.ronny_h.aoc.extensions.combinations
5+
import de.ronny_h.aoc.extensions.numbers.sumOfFirstNaturalNumbers
6+
import de.ronny_h.aoc.extensions.threedim.Vector
7+
import de.ronny_h.aoc.extensions.threedim.Vector.Companion.ZERO
8+
import de.ronny_h.aoc.extensions.threedim.times
9+
10+
fun main() = ParticleSwarm().run(344, 404)
11+
12+
class ParticleSwarm : AdventOfCode<Int>(2017, 20) {
13+
override fun part1(input: List<String>): Int {
14+
val allParticles = input.parseParticles()
15+
16+
// in the long term, the particles with minimal absolute acceleration stay closest to ZERO
17+
val sorted = allParticles.withIndex().sortedBy { it.value.acceleration.abs() }
18+
val minAcceleration = sorted.first().value.acceleration.abs()
19+
var particles = sorted.takeWhile { it.value.acceleration.abs() == minAcceleration }
20+
21+
particles.forEach { println("${it.index}: ${it.value.acceleration.abs()} - ${it.value}") }
22+
23+
// for equal absolute acceleration, their direction and initial velocity are relevant
24+
// -> simulate a significant amount of ticks
25+
var closest = 0
26+
var oldClosest: Int
27+
var closestIsTheSameCount = 0
28+
var iterations = 0
29+
30+
while (closestIsTheSameCount < 10000) {
31+
iterations++
32+
oldClosest = closest
33+
particles = particles.map { IndexedValue(it.index, it.value.update()) }
34+
closest = particles.map { IndexedValue(it.index, it.value.position taxiDistanceTo ZERO) }
35+
.minBy { it.value }
36+
.index
37+
closestIsTheSameCount += if (oldClosest == closest) 1 else 0
38+
}
39+
40+
println("found closest in $iterations iterations")
41+
return closest
42+
}
43+
44+
override fun part2(input: List<String>): Int {
45+
// for one particle:
46+
// p_i = p_0 + i*v_0 + (i*(i+1)/2)*a // i*(i+1)/2 = sumOfFirstNaturalNumbers(i)
47+
//
48+
// particles q, r collide in iteration i when:
49+
// p_q_i == p_r_i
50+
// <=> p_q_0 + i*v_q_0 + (i*(i+1)/2)*a_q == p_r_0 + i*v_r_0 + (i*(i+1)/2)*a_r
51+
// <=> 0 == p_q_0 - p_r_0 + i*(v_q_0 - v_r_0) + (i*(i+1)/2)*(a_q - a_r)
52+
53+
val particles = input.parseParticles().withIndex().toList()
54+
val toRemove = mutableSetOf<Int>()
55+
56+
for (i in 0..100) {
57+
particles
58+
.filterNot { it.index in toRemove }
59+
.combinations()
60+
.forEach {
61+
if (ZERO == differenceOf(it.first.value, it.second.value, i)) {
62+
toRemove.add(it.first.index)
63+
toRemove.add(it.second.index)
64+
}
65+
}
66+
if (i % 10 == 0) {
67+
println("$i: ${particles.size - toRemove.size}")
68+
}
69+
}
70+
71+
return particles.size - toRemove.size
72+
}
73+
74+
private fun differenceOf(q: Particle, r: Particle, i: Int): Vector {
75+
return q.position - r.position + i * (q.velocity - r.velocity) + sumOfFirstNaturalNumbers(i) * (q.acceleration - r.acceleration)
76+
}
77+
}
78+
79+
data class Particle(val position: Vector, val velocity: Vector, val acceleration: Vector) {
80+
fun update(): Particle {
81+
val newVelocity = velocity + acceleration
82+
return Particle(position + newVelocity, newVelocity, acceleration)
83+
}
84+
}
85+
86+
fun List<String>.parseParticles() = map {
87+
val (p, v, a) = it.split(", ")
88+
Particle(
89+
position = p.toVector("p"),
90+
velocity = v.toVector("v"),
91+
acceleration = a.toVector("a"),
92+
)
93+
}
94+
95+
private fun String.toVector(name: String): Vector {
96+
val (x, y, z) = substringAfter("$name=<")
97+
.substringBefore(">")
98+
.split(",")
99+
.map(String::trim)
100+
.map(String::toLong)
101+
return Vector(x, y, z)
102+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package de.ronny_h.aoc.year2017.day20
2+
3+
import de.ronny_h.aoc.extensions.asList
4+
import de.ronny_h.aoc.extensions.threedim.Vector
5+
import io.kotest.core.spec.style.StringSpec
6+
import io.kotest.matchers.shouldBe
7+
8+
class ParticleSwarmTest : StringSpec({
9+
10+
val input = """
11+
p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0>
12+
p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0>
13+
""".asList()
14+
15+
"input can be parsed" {
16+
input.parseParticles() shouldBe listOf(
17+
Particle(Vector(3, 0, 0), Vector(2, 0, 0), Vector(-1, 0, 0)),
18+
Particle(Vector(4, 0, 0), Vector(0, 0, 0), Vector(-2, 0, 0)),
19+
)
20+
}
21+
22+
"a particle can be updated" {
23+
Particle(
24+
Vector(3, 2, 1),
25+
Vector(2, 1, -1),
26+
Vector(-1, 1, 2)
27+
).update() shouldBe Particle(
28+
Vector(4, 4, 2),
29+
Vector(1, 2, 1),
30+
Vector(-1, 1, 2)
31+
)
32+
}
33+
34+
"part 1: the particle that will stay closest to position <0,0,0> in the long term" {
35+
ParticleSwarm().part1(input) shouldBe 0
36+
}
37+
38+
"part 2: only one particle is left after all collisions are resolved" {
39+
val input = """
40+
p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0>
41+
p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0>
42+
p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0>
43+
p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0>
44+
""".asList()
45+
ParticleSwarm().part2(input) shouldBe 1
46+
}
47+
})

0 commit comments

Comments
 (0)