123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- package main
-
- import (
- "fmt"
- "github.com/csmith/aoc-2019/common"
- "sync"
- )
-
- func readInput(file string) (pos [3][]int64, vel [3][]int64) {
- for _, line := range common.ReadFileAsStrings(file) {
- var x, y, z int64
- if _, err := fmt.Sscanf(line, "<x=%d, y=%d, z=%d>", &x, &y, &z); err != nil {
- panic(fmt.Sprintf("unable to parse line '%v': %v", line, err))
- }
-
- pos[0] = append(pos[0], x)
- pos[1] = append(pos[1], y)
- pos[2] = append(pos[2], z)
- vel[0] = append(vel[0], 0)
- vel[1] = append(vel[1], 0)
- vel[2] = append(vel[2], 0)
- }
- return
- }
-
- func attract(p1, p2 int64, dp1, dp2 *int64) {
- if p1 < p2 {
- *dp1++
- *dp2--
- } else if p1 > p2 {
- *dp1--
- *dp2++
- }
- }
-
- func step(pos, vel []int64) {
- for x := 0; x < len(pos); x++ {
- for y := x + 1; y < len(pos); y++ {
- attract(pos[x], pos[y], &vel[x], &vel[y])
- }
- }
-
- for i, v := range vel {
- pos[i] += v
- }
- }
-
- func static(vel []int64) bool {
- for _, v := range vel {
- if v != 0 {
- return false
- }
- }
- return true
- }
-
- func energy(channel chan []int64, moons int) int64 {
- sums := make([]int64, moons*2)
- for i := 0; i < 3; i++ {
- row := <-channel
- for n, v := range row {
- sums[n] += common.Abs(v)
- }
- }
-
- energy := int64(0)
- for i := 0; i < len(sums)/2; i++ {
- energy += sums[i] * sums[len(sums)/2+i]
- }
- return energy
- }
-
- // Sweeping assumptions/notes on how this mess works:
- //
- // 1) the movement of the planets will be parabolic - they'll accelerate towards each other,
- // then gradually slow down to a complete stop and reverse path going back to the starting point.
- // I'm not sure I can prove that's the case in general, but it does seem to hold true.
- //
- // 2) the middle of the parabola occurs after step 1000 (otherwise the code would need a fiddly bit of
- // state tracking to ensure it returned a value for part 1).
- //
- // 3) because the axes are independent, they can be simulated in parallel, and their individual parabolic inflection
- // points found. This gives us the loop count for each axis (2x the number of steps to reach the inflection point),
- // and we can find the first time all three axis's loops intersect by finding the lowest common multiple of those
- // three values.
- //
- // e.g.
- //
- // <------------------- position on axis ------------------>
- //
- // __,..--'"" |
- // step 1000 _,..--'"" ^ |
- // v _,..-'"" start |
- // _,..-'" vel = 0 |
- // _,.-'" |
- // _.-'" |
- // _.-" |
- // .-' |
- // .' |
- // / |
- // ; } inflection point steps
- // ; } vel = 0, step = n |
- // \ |
- // `. |
- // `-. |
- // "-._ |
- // "`-._ |
- // "`-.,_ |
- // "`-..,_ |
- // ""`-..,_ |
- // ""`--..,_ |
- // ""`--..,__ V
- // ^
- // back to original position
- // vel = 0, step = 2n
- func main() {
- pos, vel := readInput("12/input.txt")
-
- part1wg, part1chan := &sync.WaitGroup{}, make(chan []int64, 3)
- part2wg, part2chan := &sync.WaitGroup{}, make(chan int64, 3)
- for i, ps := range pos {
- part1wg.Add(1)
- part2wg.Add(1)
-
- go func(pos, vel []int64) {
- for n := int64(0); ; n++ {
- step(pos, vel)
-
- if n+1 == 1000 {
- part1chan <- append(pos, vel...)
- part1wg.Done()
- }
-
- if static(vel) {
- part2chan <- 2 * (n + 1)
- part2wg.Done()
- return
- }
- }
- }(ps, vel[i])
- }
-
- part1wg.Wait()
- println(energy(part1chan, len(pos[0])))
-
- part2wg.Wait()
- println(common.LCM(<-part2chan, <-part2chan, <-part2chan))
- }
|