Score 263 385 425 426 with only primes < 1.000.000 (was: non-competitive, now it is; score can be increased by running the program longer)
I followed the same path as Wheat Wizard: iteratively search for primes in the solution that can be replaced with a longer list of primes with the same result. I wrote that Python program that does exactly this. It starts with solution S = {2}
and than iterates of all elements of that solution and tries to find a decomposition of that prime for which 1/(p-1) = sum(1/(q-1) for all q in the decomposition.
After I realized that S should be a set (and not a list), I altered the program to take this into account. I also added a ton of performance optimizations. The solution of 263 came up within 200 seconds or so (running under pypy3), but if you let it running, it steadily keeps coming with additional (longer) solutions.
Current best solution (425 elements, with all primes < 1M, calculated in ~ 15 min.):
S = [3, 5, 13, 19, 29, 37, 103, 151, 241, 281, 409, 541, 577, 593, 661, 701, 751, 1297, 1327, 2017, 2161, 2251, 2293, 2341, 2393, 2521, 2593, 2689, 2731, 3061, 3079, 3329, 3361, 3457, 6301, 6553, 7057, 7177, 7481, 7561, 8737, 9001, 9241, 9341, 10501, 11617, 12097, 12547, 14281, 14449, 14561, 15121, 17761, 17851, 18217, 18481, 20593, 21313, 22441, 23189, 23761, 24571, 26041, 26881, 28351, 28513, 29641, 30241, 36529, 37441, 46993, 49921, 51169, 57331, 58109, 58313, 58369, 58831, 59659, 60737, 60757, 61001, 61381, 61441, 61561, 61609, 63067, 63601, 64513, 64901, 65053, 65089, 65701, 65881, 66301, 66931, 67049, 69389, 69941, 70181, 72161, 72481, 72577, 72661, 73061, 73699, 74521, 77521, 78241, 79693, 81181, 86951, 88741, 90631, 98011, 100297, 102181, 107641, 108991, 109201, 109537, 114913, 117841, 118429, 121993, 122761, 123001, 124561, 127601, 128629, 130073, 130969, 131561, 133387, 133813, 138181, 138403, 139501, 146077, 149521, 159457, 160081, 162289, 163543, 166601, 174241, 175891, 176401, 177913, 180181, 182711, 189421, 199921, 201781, 206641, 218527, 223441, 227089, 229739, 234961, 238081, 238141, 238897, 239851, 246241, 250057, 261577, 266401, 267961, 280321, 280837, 280897, 281233, 283501, 283861, 284161, 287233, 288049, 291721, 297601, 299053, 302221, 306853, 309629, 313153, 316681, 322057, 325921, 332489, 342211, 342241, 349981, 352273, 354961, 355321, 360977, 365473, 379177, 390097, 390961, 394717, 395627, 401057, 404251, 404489, 412127, 412651, 416881, 417649, 418027, 424117, 427681, 428221, 428401, 429409, 430921, 434521, 435481, 441937, 443873, 444641, 451441, 453601, 454609, 455149, 459649, 466201, 468001, 473617, 474241, 480737, 481693, 483883, 496471, 498301, 498961, 499141, 499591, 499969, 501601, 501841, 502633, 513067, 514513, 517609, 523261, 524521, 525313, 529381, 538721, 540541, 545161, 550117, 552553, 560561, 562633, 563501, 563851, 568177, 570781, 575723, 587497, 590669, 591193, 599281, 601801, 601903, 604001, 605551, 607993, 609589, 611389, 617401, 621007, 627301, 628561, 628993, 629281, 635449, 637201, 639211, 642529, 645751, 651361, 651857, 653761, 654853, 655453, 657091, 662941, 664633, 667801, 669121, 669901, 670177, 673201, 673921, 675109, 688561, 689921, 691363, 692641, 694033, 695641, 697681, 698293, 700591, 703081, 703561, 705169, 705181, 707071, 709921, 713627, 732829, 735373, 737413, 739861, 742369, 745543, 750121, 750721, 754771, 756961, 757063, 758753, 759001, 760321, 761671, 762721, 766361, 773501, 774181, 776557, 779101, 782461, 784081, 784981, 786241, 788317, 794641, 795601, 797273, 800089, 801469, 808081, 808177, 810151, 813121, 815671, 819017, 823481, 823621, 825553, 831811, 833281, 833449, 836161, 839161, 840911, 846217, 859657, 859861, 860609, 863017, 865801, 869251, 870241, 875521, 876929, 878011, 880993, 884269, 891893, 895681, 898921, 899263, 902401, 904861, 905761, 907369, 908129, 914861, 917281, 917317, 921601, 922321, 923833, 926377, 939061, 941641, 942401, 943009, 943273, 944161, 944821, 944833, 949621, 949961, 950041, 950401, 953437, 953443, 954001, 957349, 957529, 960121, 960961, 963901, 964783, 967261, 967627, 967751, 968137, 971281, 973561, 973591, 984127, 984341, 984913, 986437, 991381, 992941, 994561, 995347, 996001]
Proof that is satisfies the challenge:
Some of the decompositions used:
1/( 2-1) = sum(1/(p-1)) for p in: {(5, 7, 13, 3)}
1/( 3-1) = sum(1/(p-1)) for p in: {(7, 13, 5)}
1/( 5-1) = sum(1/(p-1)) for p in: {(13, 7)}
1/( 7-1) = sum(1/(p-1)) for p in: {(13, 19, 37)}
1/( 13-1) = sum(1/(p-1)) for p in: {(19, 37)}
1/( 19-1) = sum(1/(p-1)) for p in: {(29, 71, 181)}
1/( 29-1) = sum(1/(p-1)) for p in: {(37, 127)}
1/( 37-1) = sum(1/(p-1)) for p in: {(43, 281, 2521)}
1/( 43-1) = sum(1/(p-1)) for p in: {(53, 223, 13469)}
...
1/(8779-1) = sum(1/(p-1)) for p in: {(8969, 739861, 941641)}
1/(8807-1) = sum(1/(p-1)) for p in: {(9001, 773501, 865801)}
1/(8821-1) = sum(1/(p-1)) for p in: {(8941, 657091)}
1/(8941-1) = sum(1/(p-1)) for p in: {(9041, 808177)}
1/(8969-1) = sum(1/(p-1)) for p in: {(9463, 227089, 705169)}
1/(9001-1) = sum(1/(p-1)) for p in: {(9109, 759001)}
1/(9041-1) = sum(1/(p-1)) for p in: {(9241, 417649)}
1/(9109-1) = sum(1/(p-1)) for p in: {(10891, 55661)}
1/(9181-1) = sum(1/(p-1)) for p in: {(9343, 529381)}
1/(9241-1) = sum(1/(p-1)) for p in: {(9341, 863017)}
1/(9341-1) = sum(1/(p-1)) for p in: {(15121, 25219, 784561)}
1/(9343-1) = sum(1/(p-1)) for p in: {(9689, 261577)}
1/(9463-1) = sum(1/(p-1)) for p in: {(10957, 69389)}
1/(9689-1) = sum(1/(p-1)) for p in: {(12457, 43597)}
1/(10333-1) = sum(1/(p-1)) for p in: {(10501, 645751)}
...
1/(131561-1) = sum(1/(p-1)) for p in: {(237361, 295153)}
1/(166601-1) = sum(1/(p-1)) for p in: {(243433, 527851)}
1/(266401-1) = sum(1/(p-1)) for p in: {(386401, 857809)}
1/(355321-1) = sum(1/(p-1)) for p in: {(639577, 799471)}
Python3 code:
import itertools
import sympy
import cProfile
import functools
import bisect
import operator
def sundaram3(max_n):
# Returns a list of all primes under max_n
numbers = list(range(3, max_n + 1, 2))
half = (max_n) // 2
initial = 4
for step in range(3, max_n + 1, 2):
for i in range(initial, half, step):
numbers[i - 1] = 0
initial += 2 * (step + 1)
if initial > half:
return [2] + list([_f for _f in numbers if _f])
# Precalculate all primes up to a million to speed things up
PRIMES_TO_1M = list(sundaram3(1000000))
def nextprime(number):
# partly precalculated fast version for calculating the
# first (e.g. smallest) prime that is largest than numer
global PRIMES_TO_1M
if number <= PRIMES_TO_1M[-2]:
return PRIMES_TO_1M[bisect.bisect(PRIMES_TO_1M, number)]
return sympy.nextprime(number)
def isprime(number):
# partly precalculated fast version to determine of number is prime
global PRIMES_TO_1M
if number < 1000000:
return number in PRIMES_TO_1M
return sympy.isprime(number)
def upper_limit(prime, length=2):
# Returns the largest prime q in the decomposition of prime with the given
# length such that 1 / (prime - 1) = sum( 1 / (q - 1)) for q in
# set V, with V has the given length.
# ASSUMPTION: all q are unique; this assumption is not validated,
# but for this codegolf, the solution must be a set, so this is safe.
if length == 1:
return prime
nextp = nextprime(prime)
largestprime = (prime * nextp - 2 * prime + 1 ) // (nextp - prime)
if not isprime(largestprime):
largestprime = nextprime(largestprime)
return upper_limit(largestprime, length - 1)
def find_decomposition(prime, length=2):
# Returns a list of primes V = {q1, q2, q3, ...} for which holds that:
# 1 / (prime - 1) = sum(1 / (q - 1)) for q in V.
# Returns None if this decomposition is not found.
# Note that there may be more than one V of a given prime, but this
# function returns the first found V (implementation note: the sortest one)
print(f"Searching decomposition for prime {prime} with length {length} in range ({prime}, {upper_limit(prime, length)}]")
prime_range = PRIMES_TO_1M[bisect.bisect(PRIMES_TO_1M, prime) + 1:
bisect.bisect(PRIMES_TO_1M, upper_limit(prime, length) + 1)]
# we only search for combinations of length -1; the last factor is calculated
for combi in itertools.combinations(prime_range, length - 1):
# we find the common factor of prime and all primes in combi
# and use that to calculate the remaining prime. This is faster
# than trying all prime combinations.
factoritems = [-prime + 1] + [c - 1 for c in combi]
factor = -functools.reduce(operator.mul, factoritems)
remainder = - factor / sum(factor // p for p in factoritems) + 1
if remainder == int(remainder) and isprime(remainder) and remainder not in combi:
combi = combi + (int(remainder),)
print(f"Found decomposition: {combi}")
return combi
return None
def find_solutions():
# Finds incrementally long solutions for the set of primes S, for which:
# sum(1/(p-1)) == 1 for p in S.
# We do this by incrementally searching for primes in S that can be
# replaced by longer subsets that have the same value of sum(1/p-1).
# These replacements are stored in a dictionary "decompositions".
# Starting with the base solution S = [2] and all decompositions,
# you can construct S.
decompositions = {} # prime: ([decomposition], max tried length)
S = [2]
old_solution_len = 0
# Keep looping until there are no decompositions that make S longer
while len(S) > old_solution_len:
# Loop over all primes in S to search for decompositions
for p in sorted(set(S)):
# If prime p is not in the decompositions dict, add it
if p not in decompositions:
decompositions[p] = (find_decomposition(p, 2), 2)
# If prime p is in the decompositions dict, but without solution,
# try to find a solution 1 number longer than the previous try
elif not decompositions[p][0]:
length = decompositions[p][1] + 1
decompositions[p] = (find_decomposition(p, length), length)
# If prime p is in decompositions and it has a combi
# and the combi is not already in S, replace p with the combi
elif all(p not in S for p in decompositions[p][0]):
old_solution_len = len(S)
print(f"Removing occurence of {p} with {decompositions[p][0]}")
S.remove(p)
S.extend(decompositions[p][0])
S = sorted(S)
break # break out of the for loop
print(f"Found S with length {len(S)}: S = {S}")
print(f"Decompositions: ")
for prime in sorted(decompositions.keys()):
print(f" 1/({prime:3}-1) = sum(1/(p-1)) for p in: \u007b{decompositions[prime][0]}\u007d")
def main():
cProfile.run("find_solutions()")
if __name__ == "__main__":
main()