euler/python/e128.py

212 lines
5.5 KiB
Python
Raw Normal View History

2024-04-13 14:56:55 +02:00
from lib_prime import primes, is_prime_rabin_miller
from functools import lru_cache
def first_approach():
""" This was my original naiv approach that worked, but was way to slow
because we computed the whole hex tile grid. """
ps = set(primes(1000000))
hextiles = {(0, 0): 1}
add_ring(1, hextiles)
add_ring(2, hextiles)
# Visualization of how the coord system for the hex tile grid works.
#
# 2 1 0 1 2
# 4 8
# 3 9 19
# 2 10 2 18
# 1 3 7
# 0 11 1 17
# 1 4 6
# 2 12 5 16
# 3 13 15
# 4 14
#
#
pds = [1, 2, 8]
for n in range(0, 100):
ring_coord = ring_start(n)
print(" ", ring_coord, hextiles[ring_coord])
if n % 10 == 0:
print(n)
add_ring(n + 1, hextiles)
for coord in ring_coords(n):
pdv = pd(coord, hextiles, ps)
if pdv == 3:
v = hextiles[coord]
assert v == first_number_ring(n) or v == first_number_ring(n + 1) - 1
pds.append(hextiles[coord])
print(len(pds))
print(sorted(pds))
target = 10
if len(pds) >= target:
return sorted(pds)[target - 1]
return None
def ring_start(n):
return (-n * 2, 0)
@lru_cache
def first_number_ring(n):
if n == 0:
return 1
elif n == 1:
return 2
else:
return first_number_ring(n - 1) + (n - 1) * 6
def get_nbvs_ring_start(n):
# 8 1
# 9 19 2 6
# 2 0
# 3 7 3 5
# l 4
v0 = first_number_ring(n)
v1 = first_number_ring(n + 1)
v2 = v1 + 1
v3 = v0 + 1
v4 = first_number_ring(n - 1)
v5 = v1 - 1
v6 = first_number_ring(n + 2) - 1
nbvs = [v1, v2, v3, v4, v5, v6]
# print(v0, nbvs)
return nbvs
def pd_ring_start(n):
v0 = first_number_ring(n)
nbvs = get_nbvs_ring_start(n)
r = 0
for v in nbvs:
if is_prime_rabin_miller(abs(v0 - v)):
r += 1
return r
def get_nbvs_ring_prev(n):
""" Get neighbors and values for tile before top tile. """
v0 = first_number_ring(n + 1) - 1
v1 = first_number_ring(n + 2) - 1
v2 = first_number_ring(n)
v3 = first_number_ring(n - 1)
v4 = v2 - 1
v5 = first_number_ring(n + 1) - 2
v6 = v1 - 1
nbvs = [v1, v2, v3, v4, v5, v6]
# print(v0, nbvs)
return nbvs
def pd_ring_prev(n):
v0 = first_number_ring(n + 1) - 1
nbvs = get_nbvs_ring_prev(n)
r = 0
for v in nbvs:
if is_prime_rabin_miller(abs(v0 - v)):
r += 1
return r
def add_ring(n, numbers):
""" Adds ring n coords and values to numbers. """
if n == 0:
return
first_coord = ring_start(n)
current_number = first_number_ring(n)
numbers[first_coord] = current_number
current_coord = tuple(first_coord)
for ro, co in [(1, -1), (2, 0), (1, 1), (-1, 1), (-2, 0), (-1, -1)]:
for _ in range(n):
current_coord = (current_coord[0] + ro, current_coord[1] + co)
current_number += 1
numbers[current_coord] = current_number
# Reset first coord which is overriden.
numbers[first_coord] = first_number_ring(n)
def get_neighbor_values(coord, numbers):
neighbors = []
for ro, co in [(-2, 0), (-1, 1), (1, 1), (2, 0), (1, -1), (-1, -1)]:
nc = (coord[0] + ro, coord[1] + co)
neighbors.append(numbers[nc])
return neighbors
def ring_coords(n):
""" Returns coords for ring n. """
if n == 0:
yield (0, 0)
current_coord = ring_start(n)
for ro, co in [(1, -1), (2, 0), (1, 1), (-1, 1), (-2, 0), (-1, -1)]:
for _ in range(n):
current_coord = (current_coord[0] + ro, current_coord[1] + co)
yield current_coord
def pd(coord, numbers, primes):
prime_delta = 0
v = numbers[coord]
for nbv in get_neighbor_values(coord, numbers):
if abs(v - nbv) in primes:
prime_delta += 1
return prime_delta
def test_get_nbvs_functions():
""" Make sure that the function to compute the neighbor values works
correctly by comparing the values to the original grid based approach. """
hextiles = {(0, 0): 1}
add_ring(1, hextiles)
add_ring(2, hextiles)
for n in range(2, 30):
add_ring(n + 1, hextiles)
nbvs1 = get_nbvs_ring_start(n)
ring_coord = ring_start(n)
nbvs2 = get_neighbor_values(ring_coord, hextiles)
assert sorted(nbvs1) == sorted(nbvs2)
nbvs1 = get_nbvs_ring_prev(n)
ring_coord = (ring_start(n)[0] + 1, ring_start(n)[1] + 1)
nbvs2 = get_neighbor_values(ring_coord, hextiles)
assert sorted(nbvs1) == sorted(nbvs2)
def euler_128():
# Conjecture: PD3s only occur at ring starts or at ring start minus one.
# Under this assumption, we can significantly reduce the search space.
# The only challenge is to find a way to directly compute the relevant
# coords and neighbor values.
test_get_nbvs_functions()
target = 2000
pds = [1, 2]
for n in range(2, 80000):
if pd_ring_start(n) == 3:
v0 = first_number_ring(n)
pds.append(v0)
if pd_ring_prev(n) == 3:
v0 = first_number_ring(n + 1) - 1
pds.append(v0)
assert len(pds) > target
return sorted(pds)[target - 1]
if __name__ == "__main__":
solution = euler_128()
print("e128.py: " + str(solution))
assert(solution == 14516824220)