Skip to content

Commit 57a4f6e

Browse files
committed
add day 12: N-Body Problem
1 parent 8b250b3 commit 57a4f6e

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

12_n_body_problem.rb

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
def step(poses, vels)
2+
poses.each_with_index { |pi, i|
3+
# pi = 2, p = 5, we want it to increase.
4+
# so we do 5 <=> 2 which is 1.
5+
vels[i] += poses.sum { |p| p <=> pi }
6+
}
7+
vels.each_with_index { |vel, i| poses[i] += vel }
8+
end
9+
10+
def run1k(moons)
11+
pos = moons.dup
12+
vel = moons.map { 0 }
13+
14+
1000.times { step(pos, vel) }
15+
pos.zip(vel)
16+
end
17+
18+
def codegen_vel_update
19+
i = (0...4).to_a
20+
i.combination(2) { |a, b|
21+
# among these options, this one seems to be fastest.
22+
puts "if p#{a} > p#{b}; v#{a} -= 1; v#{b} += 1; elsif p#{b} > p#{a}; v#{a} += 1; v#{b} -= 1; end"
23+
24+
#puts "if p#{a} > p#{b}; v#{a}] -= 1; v#{b} += 1; end"
25+
#puts "if p#{b} > p#{a}; v#{a}] += 1; v#{b} -= 1; end"
26+
27+
#puts "cmp#{a}#{b} = p#{a} <=> p#{b}"
28+
#puts "v#{a} -= cmp#{a}#{b}"
29+
#puts "v#{b} += cmp#{a}#{b}"
30+
}
31+
end
32+
33+
def period(moons)
34+
raise "Can't handle anything other than four moons" if moons.size != 4
35+
36+
p0, p1, p2, p3 = moons
37+
v0 = v1 = v2 = v3 = 0
38+
39+
t = 0
40+
41+
# A lot of code duplication, but this otherwise runs slow (> 1 second).
42+
# I probably just should use a compiled language.
43+
# It's not like I wrote this by hand though, codegen saves the day.
44+
while true
45+
if p0 > p1; v0 -= 1; v1 += 1; elsif p1 > p0; v0 += 1; v1 -= 1; end
46+
if p0 > p2; v0 -= 1; v2 += 1; elsif p2 > p0; v0 += 1; v2 -= 1; end
47+
if p0 > p3; v0 -= 1; v3 += 1; elsif p3 > p0; v0 += 1; v3 -= 1; end
48+
if p1 > p2; v1 -= 1; v2 += 1; elsif p2 > p1; v1 += 1; v2 -= 1; end
49+
if p1 > p3; v1 -= 1; v3 += 1; elsif p3 > p1; v1 += 1; v3 -= 1; end
50+
if p2 > p3; v2 -= 1; v3 += 1; elsif p3 > p2; v2 += 1; v3 -= 1; end
51+
52+
p0 += v0
53+
p1 += v1
54+
p2 += v2
55+
p3 += v3
56+
57+
t += 1
58+
59+
# Given each state, there is only one previous state that could have led to it.
60+
# Because of this, the initial state is guaranteed to be the first repeat state.
61+
62+
# Further, consider any state with velocities all 0:
63+
# t-1: [(p0 , ?), (p1 , ?), (p2, 0), (p3 , ?)]
64+
# t : [(p0 , 0), (p1 , 0), (p2, 0), (p3 , 0)]
65+
# t+1: [(p0+v0, v0), (p1+v1, v1), (p2+v2, v2), (p3+v3, v3)]
66+
#
67+
# We see that positions at t-1 must be equal to positions at t, because velocities ended at 0.
68+
# Since positions are the same, velocity deltas are the same, which means we know more:
69+
#
70+
# t-2: [(p0+v0, ?), (p1+v1, ?), (p2+v2, ?), (p3+v3, ?)]
71+
# t-1: [(p0 , -v0), (p1 , -v1), (p2, -v2), (p3 , -v3)]
72+
# t : [(p0 , 0), (p1 , 0), (p2, 0), (p3 , 0)]
73+
# t+1: [(p0+v0, v0), (p1+v1, v1), (p2+v2, v2), (p3+v3, v3)]
74+
#
75+
# Denoting the delta in velocity at times t+1 and t-2 (which are the same) as a0, a1, a2, a3, then we have:
76+
#
77+
# t-3: [(p0+2*v0+a0, ?), (p1+2*v1+a1, ?), (p2+2*v2+a2, ?), (p3+2*v3+a3, ?)]
78+
# t-2: [(p0+v0, -v0-a0), (p1+v1, -v1-a1), (p2+v2, -v2-a2), (p3+v3, -v3-a3)]
79+
# ...
80+
# t+2: [(p0+2*v0+a0, v0+a0), (p1+2*v1+a1, v1+a1), (p2+2*v2+a2, v2+a2), (p3+2*v3+a3, v3+a3)]
81+
#
82+
# This process continues to repeat.
83+
# So we have this symmetry in velocities on either side of v=0.
84+
# So, if we ever reach a position with velocities 0, we certainly return to the initial state in t*2.
85+
# We could just continue to run the simulation to be sure, but might as well cut runtime in half, right?
86+
return t * 2 if v0 == 0 && v1 == 0 && v2 == 0 && v3 == 0
87+
end
88+
end
89+
90+
verbose = ARGV.delete('-v')
91+
92+
coordinates = ARGF.map { |l| l.scan(/-?\d+/).map(&method(:Integer)) }.transpose.map(&:freeze).freeze
93+
94+
moons1k = coordinates.map(&method(:run1k)).transpose
95+
puts moons1k.sum { |moon| moon.transpose.map { |c| c.sum(&:abs) }.reduce(:*) }
96+
97+
periods = coordinates.map(&method(:period))
98+
p periods if verbose
99+
puts periods.reduce(1) { |a, b| a.lcm(b) }

0 commit comments

Comments
 (0)