from math import sqrt def proper_divisors(n): """ Returns the list of divisors for n excluding n. """ if n < 2: return [] divisors = [1, ] d = 2 while d * d <= n: if n % d == 0: divisors.append(d) d += 1 return divisors def triples_brute_force(b_max): squares = {n * n: n for n in range(1, b_max)} triples = [] for b in range(3, b_max): for a in range(2, b): c_squared = a * a + b * b if c_squared in squares: triples.append((a, b, squares[c_squared])) return triples def triples_euclids_formula(m, n): """ Does not return all triples! """ assert(m > n) assert(n > 0) a = m * m - n * n b = 2 * m * n c = m * m + n * n return (a, b, c) def dicksons_method(r): triples = [] assert(r % 2 == 0) st = r * r // 2 for d in proper_divisors(st): s = d t = st // s a = r + s b = r + t c = r + s + t L = a + b + c triples.append(L) return sorted(triples) def dickson_method_min_max_perimeter(r): """ I used this method to check that the min and max perimeter for increasing r are also increasing strictly monotonic. """ assert(r % 2 == 0) st = r * r // 2 L_min = 10**8 L_max = 0 for d in proper_divisors(st): s = d t = st // s L = r + s + r + t + r + s + t if L > L_max: L_max = L if L < L_min: L_min = L return (L_min, L_max) def dicksons_method_efficient(r): L_max = 1500000 perimeters = [] assert(r % 2 == 0) st = r * r // 2 s_max = int(sqrt(st)) for s in range(s_max, 0, -1): if st % s == 0: t = st // s L = r + s + r + t + r + s + t if L > L_max: break perimeters.append(L) else: pass return perimeters def euler_075(): """ We are using Dickson's method to get all Pythagorean triples. The challenge is that this method requires finding the divisors for a given r. To make it more efficient we stop iterating after the perimeter exceeds the threshold L_max for any given r. In addition we found a r_max of 258000 empirically. This runs for 15 minutes with cpython and in under 5 minutes in pypy. It would have been better to use a generator function that finds native solutions and then extrapolate with factors k in range(2, 1500000) while L < L_max. https://en.wikipedia.org/wiki/Formulas_for_generating_Pythagorean_triples#Dickson's_method XXX: Can be strongly optimized. """ r_max = 258000 perimeter_counts = {} for r in range(2, r_max, 2): if r % 10000 == 0: print(r) for perimeter in dicksons_method_efficient(r): try: perimeter_counts[perimeter] += 1 except KeyError: perimeter_counts[perimeter] = 1 one_integer_sided_right_triangle_count = 0 for perimeter, count in perimeter_counts.items(): if count == 1: one_integer_sided_right_triangle_count += 1 return one_integer_sided_right_triangle_count if __name__ == "__main__": print("e075.py: " + str(euler_075())) assert(euler_075() == 161667)