Skip to content

Commit 413ab9a

Browse files
Solve PE 816
1 parent eef8772 commit 413ab9a

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed

pdm.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

project_euler/test_pe816.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# <https://projecteuler.net/problem=816>
2+
# <p>We create an array of points $P_n$ in a two dimensional plane using the following random number generator:<br>
3+
# $s_0=290797$<br>
4+
# $s_{n+1}={s_n}^2 \bmod 50515093$
5+
# <br> <br>
6+
# $P_n=(s_{2n},s_{2n+1})$</p>
7+
# <p>
8+
# Let $d(k)$ be the shortest distance of any two (distinct) points among $P_0, \cdots, P_{k - 1}$.<br>
9+
# E.g. $d(14)=546446.466846479$.
10+
# </p>
11+
# <p>
12+
# Find $d(2_000_000)$. Give your answer rounded to $9$ places after the decimal point.
13+
# </p>
14+
# Notes:
15+
# - Shortest distance
16+
# - <https://stackoverflow.com/a/61981008/4769802>
17+
# - <https://en.wikipedia.org/wiki/Closest_pair_of_points_problem>
18+
# - expected O(n) Rabin Lipton Closest Pair
19+
# - <https://stackoverflow.com/questions/5009423/rabins-nearest-neighbor-closest-pair-of-points-algorithm>
20+
# - <https://rjlipton.com/2009/03/01/rabin-flips-a-coin/>
21+
# - <https://ecommons.cornell.edu/server/api/core/bitstreams/ff0fc914-dd9f-4cee-9ff1-2f9775cff490/content>
22+
# - <https://www.cs.umd.edu/~samir/grant/cp.pdf>
23+
# - Check the period of the random number sequence
24+
25+
import math
26+
import itertools
27+
import time
28+
import random
29+
30+
def rabin_lipton_shortest_distance(points):
31+
if len(points) < 3:
32+
return distance(points[0],points[1])
33+
minimum_guess = sampled_shortest_distance(points)
34+
first_buckets = generate_buckets(minimum_guess, points)
35+
approximate_minimum = get_shortest_distance_from_buckets(minimum_guess, first_buckets)
36+
return approximate_minimum
37+
38+
def get_shortest_distance_from_buckets(min_distance, buckets):
39+
for key, pnts in buckets.items():
40+
if len(pnts) > 1:
41+
min_distance = min(min_distance, rabin_lipton_shortest_distance(pnts))
42+
return min_distance
43+
44+
def sampled_shortest_distance(points):
45+
sample_size = max(1, int(len(points)**.5))
46+
sample_distances = map(lambda pts: distance(pts[0], pts[1]), [ random.sample(points, 2) for i in range(sample_size) ])
47+
return min(sample_distances)
48+
49+
def generate_buckets(interval_size, points):
50+
buckets = {}
51+
for point in points:
52+
x = point[0] // interval_size
53+
y = point[1] // interval_size
54+
bucket = buckets.get((x,y), [])
55+
buckets[x,y] = bucket + [point]
56+
return buckets
57+
58+
def test_rabin_lipton_shortest_distance():
59+
a = [0,0]
60+
b = [0,1]
61+
assert 1 == rabin_lipton_shortest_distance([a,b])
62+
a = [1,1]
63+
b = [0,0]
64+
assert 2**.5 == rabin_lipton_shortest_distance([a,b])
65+
a = [0,0]
66+
b = [0,5]
67+
c = [0,1]
68+
assert 1 == rabin_lipton_shortest_distance([a,b,c])
69+
70+
def dumb_shortest_distance(points):
71+
return min(map(lambda pnts: distance(pnts[0],pnts[1]), itertools.combinations(points,2)))
72+
73+
def test_dumb_shortest_distance():
74+
a = [0,0]
75+
b = [0,1]
76+
assert 1 == dumb_shortest_distance([a,b])
77+
a = [1,1]
78+
b = [0,0]
79+
assert 2**.5 == dumb_shortest_distance([a,b])
80+
a = [0,0]
81+
b = [0,5]
82+
c = [0,1]
83+
assert 1 == dumb_shortest_distance([a,b,c])
84+
85+
86+
def distance(a, b):
87+
return math.dist(a,b)
88+
89+
def test_distance():
90+
a = [0,0]
91+
b = [0,1]
92+
assert 1 == distance(a,b)
93+
a = [0,0]
94+
b = [0,2]
95+
assert 2 == distance(a,b)
96+
a = [0,0]
97+
b = [1,0]
98+
assert 1 == distance(a,b)
99+
a = [1,0]
100+
b = [0,0]
101+
assert 1 == distance(a,b)
102+
a = [0,1]
103+
b = [0,0]
104+
assert 1 == distance(a,b)
105+
a = [0,1]
106+
b = [0,2]
107+
assert 1 == distance(a,b)
108+
a = [1,0]
109+
b = [2,0]
110+
assert 1 == distance(a,b)
111+
a = [1,1]
112+
b = [0,0]
113+
assert 2**.5 == distance(a,b)
114+
115+
def d(k):
116+
point_generator = next_point()
117+
points = [ next(point_generator) for i in range(k) ]
118+
return rabin_lipton_shortest_distance(points)
119+
120+
def test_d_works_with_small_stuff():
121+
point_generator = next_point()
122+
assert distance(next(point_generator), next(point_generator)) == d(2)
123+
assert 546446.466846479 == d(14)
124+
assert 14759.650571744576 == d(500)
125+
126+
def test_d_works_with_real_values():
127+
# assert 644.1311978160971 == d(10_000)
128+
# assert 594.461941590881 == d(100_000)
129+
# assert 51.22499389946279 == d(1_000_000)
130+
assert 20.8806130178211 == d(2_000_000)
131+
132+
def next_point():
133+
generator = next_s()
134+
while True:
135+
yield [ next(generator), next(generator) ]
136+
137+
def test_next_point_works_with_real_values():
138+
generator = next_point()
139+
[ next(generator) for i in range(4_000_000) ]
140+
141+
def test_next_point():
142+
point_generator = next_point()
143+
assert [290797, 629527] == next(point_generator)
144+
assert [13339144, 15552512] == next(point_generator)
145+
146+
def next_s():
147+
last_s = 290797
148+
yield last_s
149+
while True:
150+
last_s **= 2
151+
last_s %= 50515093
152+
yield last_s
153+
154+
def test_next_s_works_with_real_values():
155+
generator = next_s()
156+
[ next(generator) for i in range(4_000_000) ]
157+
158+
def test_next_s():
159+
generator = next_s()
160+
assert 290797 == next(generator)
161+
assert 629527 == next(generator)
162+
assert 13339144 == next(generator)
163+
assert 15552512 == next(generator)

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ requires-python = "==3.12.*"
88
[tool.pdm.dev-dependencies]
99
dev = [
1010
"pytest>=8.3.1",
11+
"pytest-timeout>=2.3.1",
1112
]
1213

1314
[tool.pdm.scripts]
14-
test = "pytest"
15+
test = "pytest --timeout 20"

0 commit comments

Comments
 (0)