This Python 3 notebook contains some solutions for the Project Euler challenge.
I (Lilian Besson) started in February 2015, and worked occasionally on Project Euler problems in March and April 2015. I should try to work on it again, hence this notebook...
%load_ext Cython
from typing import List
def divisors(n: int) -> List[int]:
return [k for k in range(1, n+1) if n%k == 0]
divisors(28)
[1, 2, 4, 7, 14, 28]
def number_of_divisors(n: int) -> int:
return len(divisors(n))
number_of_divisors(28)
6
def int_sqrt(n: int) -> int:
if n <= 0: return 0
if n == 1: return 1
i = 0
while i*i <= n:
i += 1
if i*i == n: return i
return i-1
import math
for i in range(10000):
assert int_sqrt(i) == math.floor(math.sqrt(i)), f"{i}"
def number_of_divisors2(n: int) -> int:
c = 0
for k in range(1, n+1):
if n%k == 0:
c += 1
return c
for n in range(1000):
assert number_of_divisors(n) == number_of_divisors2(n)
def number_of_divisors3(n: int) -> int:
if n <= 0: return 0
if n == 1: return 1
c = 0
for k in range(1, int_sqrt(n)+1):
# print(f"n = {n}, k = {k}, c = {c}")
if n%k == 0:
c += 1 if k*k == n else 2
return c
%timeit number_of_divisors(1000000)
%timeit number_of_divisors2(1000000)
%timeit number_of_divisors3(1000000)
45.7 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 52.6 ms ± 1.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 138 µs ± 1.49 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
for n in range(1000):
assert number_of_divisors(n) == number_of_divisors3(n), f"{n}"
%%cython -a
cdef int int_sqrt(int n):
if n <= 0: return 0
elif n == 1: return 1
cdef int i = 0
while i*i <= n:
i += 1
if i*i == n: return i
return i-1
cdef int number_of_divisors(int n):
if n <= 0: return 0
elif n == 1: return 1
cdef int c = 0
for k in range(1, int_sqrt(n)+1):
# print(f"n = {n}, k = {k}, c = {c}")
if n%k == 0:
c += 1 if k*k == n else 2
return c
def first_highly_divisible_triangular_number(int nb_of_divisors=5):
cdef int triangular_number = 1
cdef int i = 2
while number_of_divisors(triangular_number) < nb_of_divisors:
triangular_number += i
i += 1
return triangular_number
Generated by Cython 0.29.21
Yellow lines hint at Python interaction.
Click on a line that starts with a "+
" to see the C code that Cython generated for it.
01:
+02: cdef int int_sqrt(int n):
static int __pyx_f_46_cython_magic_b421ed9aa342829a2e7bf32576065354_int_sqrt(int __pyx_v_n) { int __pyx_v_i; int __pyx_r; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("int_sqrt", 0); /* … */ /* function exit code */ __pyx_L0:; __Pyx_RefNannyFinishContext(); return __pyx_r; }
+03: if n <= 0: return 0
__pyx_t_1 = ((__pyx_v_n <= 0) != 0); if (__pyx_t_1) { __pyx_r = 0; goto __pyx_L0; }
+04: elif n == 1: return 1
__pyx_t_1 = ((__pyx_v_n == 1) != 0); if (__pyx_t_1) { __pyx_r = 1; goto __pyx_L0; }
+05: cdef int i = 0
__pyx_v_i = 0;
+06: while i*i <= n:
while (1) { __pyx_t_1 = (((__pyx_v_i * __pyx_v_i) <= __pyx_v_n) != 0); if (!__pyx_t_1) break;
+07: i += 1
__pyx_v_i = (__pyx_v_i + 1);
+08: if i*i == n: return i
__pyx_t_1 = (((__pyx_v_i * __pyx_v_i) == __pyx_v_n) != 0); if (__pyx_t_1) { __pyx_r = __pyx_v_i; goto __pyx_L0; } }
+09: return i-1
__pyx_r = (__pyx_v_i - 1); goto __pyx_L0;
10:
+11: cdef int number_of_divisors(int n):
static int __pyx_f_46_cython_magic_b421ed9aa342829a2e7bf32576065354_number_of_divisors(int __pyx_v_n) { int __pyx_v_c; PyObject *__pyx_v_k = NULL; int __pyx_r; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("number_of_divisors", 0); /* … */ /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_6); __Pyx_XDECREF(__pyx_t_8); __Pyx_WriteUnraisable("_cython_magic_b421ed9aa342829a2e7bf32576065354.number_of_divisors", __pyx_clineno, __pyx_lineno, __pyx_filename, 1, 0); __pyx_r = 0; __pyx_L0:; __Pyx_XDECREF(__pyx_v_k); __Pyx_RefNannyFinishContext(); return __pyx_r; }
+12: if n <= 0: return 0
__pyx_t_1 = ((__pyx_v_n <= 0) != 0); if (__pyx_t_1) { __pyx_r = 0; goto __pyx_L0; }
+13: elif n == 1: return 1
__pyx_t_1 = ((__pyx_v_n == 1) != 0); if (__pyx_t_1) { __pyx_r = 1; goto __pyx_L0; }
+14: cdef int c = 0
__pyx_v_c = 0;
+15: for k in range(1, int_sqrt(n)+1):
__pyx_t_2 = __Pyx_PyInt_From_long((__pyx_f_46_cython_magic_b421ed9aa342829a2e7bf32576065354_int_sqrt(__pyx_v_n) + 1)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 15, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); __pyx_t_3 = PyTuple_New(2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_3); __Pyx_INCREF(__pyx_int_1); __Pyx_GIVEREF(__pyx_int_1); PyTuple_SET_ITEM(__pyx_t_3, 0, __pyx_int_1); __Pyx_GIVEREF(__pyx_t_2); PyTuple_SET_ITEM(__pyx_t_3, 1, __pyx_t_2); __pyx_t_2 = 0; __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_3, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 15, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; if (likely(PyList_CheckExact(__pyx_t_2)) || PyTuple_CheckExact(__pyx_t_2)) { __pyx_t_3 = __pyx_t_2; __Pyx_INCREF(__pyx_t_3); __pyx_t_4 = 0; __pyx_t_5 = NULL; } else { __pyx_t_4 = -1; __pyx_t_3 = PyObject_GetIter(__pyx_t_2); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 15, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_3); __pyx_t_5 = Py_TYPE(__pyx_t_3)->tp_iternext; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 15, __pyx_L1_error) } __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; for (;;) { if (likely(!__pyx_t_5)) { if (likely(PyList_CheckExact(__pyx_t_3))) { if (__pyx_t_4 >= PyList_GET_SIZE(__pyx_t_3)) break; #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS __pyx_t_2 = PyList_GET_ITEM(__pyx_t_3, __pyx_t_4); __Pyx_INCREF(__pyx_t_2); __pyx_t_4++; if (unlikely(0 < 0)) __PYX_ERR(0, 15, __pyx_L1_error) #else __pyx_t_2 = PySequence_ITEM(__pyx_t_3, __pyx_t_4); __pyx_t_4++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 15, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); #endif } else { if (__pyx_t_4 >= PyTuple_GET_SIZE(__pyx_t_3)) break; #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS __pyx_t_2 = PyTuple_GET_ITEM(__pyx_t_3, __pyx_t_4); __Pyx_INCREF(__pyx_t_2); __pyx_t_4++; if (unlikely(0 < 0)) __PYX_ERR(0, 15, __pyx_L1_error) #else __pyx_t_2 = PySequence_ITEM(__pyx_t_3, __pyx_t_4); __pyx_t_4++; if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 15, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); #endif } } else { __pyx_t_2 = __pyx_t_5(__pyx_t_3); if (unlikely(!__pyx_t_2)) { PyObject* exc_type = PyErr_Occurred(); if (exc_type) { if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear(); else __PYX_ERR(0, 15, __pyx_L1_error) } break; } __Pyx_GOTREF(__pyx_t_2); } __Pyx_XDECREF_SET(__pyx_v_k, __pyx_t_2); __pyx_t_2 = 0; /* … */ } __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
16: # print(f"n = {n}, k = {k}, c = {c}")
+17: if n%k == 0:
__pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_n); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 17, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); __pyx_t_6 = PyNumber_Remainder(__pyx_t_2, __pyx_v_k); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 17, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_6); __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __pyx_t_2 = __Pyx_PyInt_EqObjC(__pyx_t_6, __pyx_int_0, 0, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 17, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_2); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 17, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; if (__pyx_t_1) { /* … */ }
+18: c += 1 if k*k == n else 2
__pyx_t_2 = PyNumber_Multiply(__pyx_v_k, __pyx_v_k); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 18, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); __pyx_t_6 = __Pyx_PyInt_From_int(__pyx_v_n); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 18, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_6); __pyx_t_8 = PyObject_RichCompare(__pyx_t_2, __pyx_t_6, Py_EQ); __Pyx_XGOTREF(__pyx_t_8); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 18, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_t_8); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 18, __pyx_L1_error) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; if (__pyx_t_1) { __pyx_t_7 = 1; } else { __pyx_t_7 = 2; } __pyx_v_c = (__pyx_v_c + __pyx_t_7);
+19: return c
__pyx_r = __pyx_v_c; goto __pyx_L0;
20:
+21: def first_highly_divisible_triangular_number(int nb_of_divisors=5):
/* Python wrapper */ static PyObject *__pyx_pw_46_cython_magic_b421ed9aa342829a2e7bf32576065354_1first_highly_divisible_triangular_number(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ static PyMethodDef __pyx_mdef_46_cython_magic_b421ed9aa342829a2e7bf32576065354_1first_highly_divisible_triangular_number = {"first_highly_divisible_triangular_number", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_46_cython_magic_b421ed9aa342829a2e7bf32576065354_1first_highly_divisible_triangular_number, METH_VARARGS|METH_KEYWORDS, 0}; static PyObject *__pyx_pw_46_cython_magic_b421ed9aa342829a2e7bf32576065354_1first_highly_divisible_triangular_number(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { int __pyx_v_nb_of_divisors; PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("first_highly_divisible_triangular_number (wrapper)", 0); { static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_nb_of_divisors,0}; PyObject* values[1] = {0}; if (unlikely(__pyx_kwds)) { Py_ssize_t kw_args; const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args); switch (pos_args) { case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0); CYTHON_FALLTHROUGH; case 0: break; default: goto __pyx_L5_argtuple_error; } kw_args = PyDict_Size(__pyx_kwds); switch (pos_args) { case 0: if (kw_args > 0) { PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_nb_of_divisors); if (value) { values[0] = value; kw_args--; } } } if (unlikely(kw_args > 0)) { if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "first_highly_divisible_triangular_number") < 0)) __PYX_ERR(0, 21, __pyx_L3_error) } } else { switch (PyTuple_GET_SIZE(__pyx_args)) { case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0); CYTHON_FALLTHROUGH; case 0: break; default: goto __pyx_L5_argtuple_error; } } if (values[0]) { __pyx_v_nb_of_divisors = __Pyx_PyInt_As_int(values[0]); if (unlikely((__pyx_v_nb_of_divisors == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 21, __pyx_L3_error) } else { __pyx_v_nb_of_divisors = ((int)5); } } goto __pyx_L4_argument_unpacking_done; __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("first_highly_divisible_triangular_number", 0, 0, 1, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 21, __pyx_L3_error) __pyx_L3_error:; __Pyx_AddTraceback("_cython_magic_b421ed9aa342829a2e7bf32576065354.first_highly_divisible_triangular_number", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; __pyx_r = __pyx_pf_46_cython_magic_b421ed9aa342829a2e7bf32576065354_first_highly_divisible_triangular_number(__pyx_self, __pyx_v_nb_of_divisors); int __pyx_lineno = 0; const char *__pyx_filename = NULL; int __pyx_clineno = 0; /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } static PyObject *__pyx_pf_46_cython_magic_b421ed9aa342829a2e7bf32576065354_first_highly_divisible_triangular_number(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_nb_of_divisors) { int __pyx_v_triangular_number; int __pyx_v_i; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("first_highly_divisible_triangular_number", 0); /* … */ /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_2); __Pyx_AddTraceback("_cython_magic_b421ed9aa342829a2e7bf32576065354.first_highly_divisible_triangular_number", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); __Pyx_RefNannyFinishContext(); return __pyx_r; } /* … */ __pyx_tuple_ = PyTuple_Pack(3, __pyx_n_s_nb_of_divisors, __pyx_n_s_triangular_number, __pyx_n_s_i); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 21, __pyx_L1_error) __Pyx_GOTREF(__pyx_tuple_); __Pyx_GIVEREF(__pyx_tuple_); /* … */ __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_b421ed9aa342829a2e7bf32576065354_1first_highly_divisible_triangular_number, NULL, __pyx_n_s_cython_magic_b421ed9aa342829a2e); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 21, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_first_highly_divisible_triangula, __pyx_t_1) < 0) __PYX_ERR(0, 21, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+22: cdef int triangular_number = 1
__pyx_v_triangular_number = 1;
+23: cdef int i = 2
__pyx_v_i = 2;
+24: while number_of_divisors(triangular_number) < nb_of_divisors:
while (1) { __pyx_t_1 = ((__pyx_f_46_cython_magic_b421ed9aa342829a2e7bf32576065354_number_of_divisors(__pyx_v_triangular_number) < __pyx_v_nb_of_divisors) != 0); if (!__pyx_t_1) break;
+25: triangular_number += i
__pyx_v_triangular_number = (__pyx_v_triangular_number + __pyx_v_i);
+26: i += 1
__pyx_v_i = (__pyx_v_i + 1); }
+27: return triangular_number
__Pyx_XDECREF(__pyx_r); __pyx_t_2 = __Pyx_PyInt_From_int(__pyx_v_triangular_number); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 27, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); __pyx_r = __pyx_t_2; __pyx_t_2 = 0; goto __pyx_L0;
first_highly_divisible_triangular_number(5)
28
first_highly_divisible_triangular_number(150)
749700
first_highly_divisible_triangular_number(200)
2031120
first_highly_divisible_triangular_number(250)
2162160
first_highly_divisible_triangular_number(300)
2162160
first_highly_divisible_triangular_number(350)
17907120
first_highly_divisible_triangular_number(400)
17907120
first_highly_divisible_triangular_number(450)
17907120
first_highly_divisible_triangular_number(500)
76576500
That's slow if you test for divisors until n, but using this $\lfloor \sqrt{n} \rfloor$ trick speeds it up!
from datetime import date, timedelta
def isSunday(current_date: date) -> bool:
current_iso_date = current_date.isocalendar()
weekday = current_iso_date[2]
return weekday == 7
start_date = date(year=1901, month=1, day=1)
current_date = start_date
tomorrow = timedelta(days=+1)
max_date = date(year=2000, month=12, day=31)
number_of_sundays = 0
while current_date <= max_date:
if current_date.day == 1:
if isSunday(current_date):
number_of_sundays += 1
current_date += tomorrow
print(f"Between {start_date} and {max_date}, there were {number_of_sundays} Sundays happening the first day of a month.")
Between 1901-01-01 and 2000-12-31, there were 171 Sundays happening the first day of a month.
==> 171
import decimal
D = decimal.Decimal
Let's try with 1000 digits, and I'll just increase it if the solution is not good.
decimal.getcontext().prec = 1000
Example:
D('1') / D('7')
Decimal('0.1428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571429')
How to detect a cycle and its length:
def detect_cycle_length_str_offset(s: str, offset: int=0) -> int:
s = s[offset:]
length = 1
n = len(s)
cycle = s[:length]
while length < n:
cut_size = n//length
if s[:cut_size * length] == cycle * cut_size:
return length
cycle += s[length]
length += 1
return 0
def detect_cycle_length_str(s: str, offset: int=0) -> int:
s = s[offset:]
length = 1
n = len(s)
cycle = s[:length]
while length < n:
cut_size = n//length
if s[:cut_size * length] == cycle * cut_size:
return length
cycle += s[length]
length += 1
return 0
detect_cycle_length_str('1428571428571428571428571429')
# '142857' is a cycle so ==> 6
6
print(detect_cycle_length_str('1666666666666666666', offset=0))
print(detect_cycle_length_str('1666666666666666666', offset=1))
offset = 0 length = 1 cut_size = 19 s[offset : offset + cut_size * length] = 1666666666666666666 cycle * cut_size = 1111111111111111111 offset = 0 length = 2 cut_size = 9 s[offset : offset + cut_size * length] = 166666666666666666 cycle * cut_size = 161616161616161616 offset = 0 length = 3 cut_size = 6 s[offset : offset + cut_size * length] = 166666666666666666 cycle * cut_size = 166166166166166166 offset = 0 length = 4 cut_size = 4 s[offset : offset + cut_size * length] = 1666666666666666 cycle * cut_size = 1666166616661666 offset = 0 length = 5 cut_size = 3 s[offset : offset + cut_size * length] = 166666666666666 cycle * cut_size = 166661666616666 offset = 0 length = 6 cut_size = 3 s[offset : offset + cut_size * length] = 166666666666666666 cycle * cut_size = 166666166666166666 offset = 0 length = 7 cut_size = 2 s[offset : offset + cut_size * length] = 16666666666666 cycle * cut_size = 16666661666666 offset = 0 length = 8 cut_size = 2 s[offset : offset + cut_size * length] = 1666666666666666 cycle * cut_size = 1666666616666666 offset = 0 length = 9 cut_size = 2 s[offset : offset + cut_size * length] = 166666666666666666 cycle * cut_size = 166666666166666666 offset = 0 length = 10 cut_size = 1 s[offset : offset + cut_size * length] = 1666666666 cycle * cut_size = 1666666666 10 offset = 1 length = 1 cut_size = 18 s[offset : offset + cut_size * length] = 666666666666666666 cycle * cut_size = 666666666666666666 1
detect_cycle_length_str('1428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571429')
# '142857' is a cycle so ==> 6
6
How to detect a cycle in a decimal number of the form $\frac{1}{d}$:
def detect_cycle_length(denominator: str, nominator: str='1') -> int:
x = D(nominator) / D(denominator)
s = str(x).split('.')[1]
return detect_cycle_length_str(s)
detect_cycle_length('7')
6
Now it's easy to find a number with maximum cycle length:
def number_with_longest_cycle(start: int=2, end: int=1000) -> int:
max_cycle_length = 0
candidate_d = None
for number in range(start, end):
cycle_length = detect_cycle_length(str(number))
if cycle_length > max_cycle_length:
candidate_d = number
max_cycle_length = cycle_length
return candidate_d, max_cycle_length
number_with_longest_cycle(start=2, end=11)
# ==> 7, 6 as 1/7 has a cycle length of 6
(6, 501)
number_with_longest_cycle(start=1, end=100)
number_with_longest_cycle(start=1, end=1000)
I have not yet found a way to conclude. Working with strings and decimal.Decimal
numbers is probably a wrong idea.
set(str(15234))
{'1', '2', '3', '4', '5'}
set(''.join(map(str, range(1, 5+1))))
{'1', '2', '3', '4', '5'}
from typing import Union
def is_pandigital(n: Union[str, int], start: int=1, end: int=9):
str_n = str(n)
if len(str_n) != (end - start + 1):
return False
return set(str(n)) == set(''.join(map(str, range(start, end+1))))
print(is_pandigital(15234, start=1, end=5)) # True
print(is_pandigital(15234, start=1, end=9)) # False
True False
set_1to9 = {1,2,3,4,5,6,7,8,9}
def is_pandigital_1to9(n: Union[str, int]):
str_n = str(n)
if len(str_n) != 9:
return False
return ('0' not in str_n) and len(set(str_n)) == 9
print(is_pandigital_1to9(15234)) # False
print(is_pandigital_1to9(152346987)) # False
False True
%timeit f"{39}{186}{7254}"
%timeit str(39) + str(186) + str(7254)
179 ns ± 1.41 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) 368 ns ± 3.05 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
def are_pandigital(multiplicand: int, multiplier: int, product: int) -> bool:
# assert multiplicand * multiplier == product
n = f"{multiplicand}{multiplier}{product}"
return is_pandigital_1to9(n)
are_pandigital(39, 186, 7254)
True
from typing import Tuple
def find_pandigital_products(n: int) -> Tuple[bool, Tuple[int, int]]:
x = 2
while x*x <= n:
if n % x == 0:
y = n // x
if are_pandigital(x, y, n):
return True, (x, y)
x += 1
return False, (None, None)
find_pandigital_products(7254)
(True, (39, 186))
def all_pandigital_products(maxi=10000):
return [n for n in range(1, maxi + 1) if find_pandigital_products(n)[0]]
def sum_of_all_pandigital_products(maxi=10000):
s = 0
for n in range(1, maxi + 1):
b, (x, y) = find_pandigital_products(n)
if b:
print(f"n = {n} is pandigital with x = {x} and y = {y}, and current sum = {s}...")
s += n
return s
# return sum(all_pandigital_products())
sum_of_all_pandigital_products()
n = 4396 is pandigital with x = 28 and y = 157, and current sum = 0... n = 5346 is pandigital with x = 18 and y = 297, and current sum = 4396... n = 5796 is pandigital with x = 12 and y = 483, and current sum = 9742... n = 6952 is pandigital with x = 4 and y = 1738, and current sum = 15538... n = 7254 is pandigital with x = 39 and y = 186, and current sum = 22490... n = 7632 is pandigital with x = 48 and y = 159, and current sum = 29744... n = 7852 is pandigital with x = 4 and y = 1963, and current sum = 37376...
45228
import fractions
F = fractions.Fraction
F('49/98'), F('4/8'), F('49/98') == F('4/8')
(Fraction(1, 2), Fraction(1, 2), True)
def all_fractions_of_n_digits(n=2):
start = '1'*n
end = '9'*n
for i in range(int(start), int(end) + 1):
for j in range(i + 1, int(end) + 1):
yield F(f"{i}/{j}"), i, j
len(list(all_fractions_of_n_digits(2)))
3916
So there are 3916 fractions of $x/y$ with $x,y$ having exactly two numbers, such that $x/y \leq 1$. For each of them, we will try to remove one of the digits from $x$ and from $y$, and check if the resulting "reduced" fraction is equal to the original one. If we remove $0$, we won't count it.
It's easy to remove digits when working with strings:
si, sj = "49", "98"
si.replace("9", "", 1)
'4'
So we can check if the fraction $i/j$ can be "wrongly" simplified/reduced by such code:
def can_be_wrongly_simplified(i, j):
si, sj = str(i), str(j)
for digit in si:
if digit != '0' and digit in sj:
si_without_digit = int(si.replace(digit, "", 1))
sj_without_digit = int(sj.replace(digit, "", 1))
if sj_without_digit != 0 and F(f"{i}/{j}") == F(f"{si_without_digit}/{sj_without_digit}"):
return True
return False
Let's try it:
for frac, i, j in all_fractions_of_n_digits(2):
if can_be_wrongly_simplified(i, j):
print(f"{frac} with i = {i} and j = {j}")
1/4 with i = 16 and j = 64 1/5 with i = 19 and j = 95 2/5 with i = 26 and j = 65 1/2 with i = 49 and j = 98
So there are indeed exactly four such fractions (with exactly two digits on nominator and denominator).
Let's compute their product:
product = F(1, 1)
for frac, i, j in all_fractions_of_n_digits(2):
if can_be_wrongly_simplified(i, j):
print(f"{frac} with i = {i} and j = {j}")
product *= frac
print(f"Their product = {product}")
1/4 with i = 16 and j = 64 1/5 with i = 19 and j = 95 2/5 with i = 26 and j = 65 1/2 with i = 49 and j = 98 Their product = 1/100
Let's compute efficiently the factorial function, by using memoisation:
from functools import lru_cache
@lru_cache(maxsize=None)
def factorial(n: int) -> int:
if n <= 1: return 1
else: return n * factorial(n - 1)
factorials = [factorial(n) for n in range(0, 10)]
print(factorials)
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
n = 100
math.ceil(math.log10(n)) # => 2 incorrect, needs + 1
n = 145
math.ceil(math.log10(n)) # => 3 correct
3
First, we need to bound the size of the largest integer that can be the sum of the factorials of its digits.
Let $k$ the number of digits of $x$, for instance $k=2$. Then $x$ is between $100...0 = 10^{k-1}$ (eg $10$ for $k=2$) and $999..9 = 10^k - 1$ (eg $99 = 100-1$ for $k=2$). The sum of the factorials of the digits of $x$ is between $1 * 1! + (k-1) * 0! = 1$ and $k * 9! = 362880 k$.
And thus, as soon as $362880 k < 10^{k-1}$ then it is not possible for $x$ to be equal to the sum of the factorials of its digits.
For small values of $k$, such that $k=3$ (for the example $145$), $10^{k-1} \leq 362880 k$ but we can easily find the smallest $k$ that violates this inequality.
So the smallest $k$ that don't work is the smallest $k$ such that $362880 k < 10^{k-1}$. The right-hand side grows exponentially while the left-hand side grows linearly, so clearly there will be a solution. Intuitively, it won't be large. Let's find out manually:
k = 1
while 10**k <= 362880 * k:
k += 1
k = max(1, k - 1)
print(k)
max_nb_pb34 = 10**k - 1
print(max_nb_pb34)
6 999999
We can also easily test if a number is the sum of the factorials of its digits:
def is_sum_of_factorials_of_its_digits(n: int) -> bool:
digits = map(int, list(str(n))) # 145 => [1, 4, 5]
sum_of_factorials_of_its_digits = sum(map(factorial, digits))
return sum_of_factorials_of_its_digits == n
assert not is_sum_of_factorials_of_its_digits(144)
assert is_sum_of_factorials_of_its_digits(145)
assert not is_sum_of_factorials_of_its_digits(146)
Now we can list all the numbers up-to a certain bound, and keep the ones we want, then compute their sum:
def list_of_such_numbers_pb34(bound=max_nb_pb34):
for i in range(3, bound + 1):
if is_sum_of_factorials_of_its_digits(i):
yield i
def sum_of_such_numbers_pb34(bound=max_nb_pb34):
return sum(list_of_such_numbers_pb34(bound=bound))
For numbers up-to $1000$, only 145 works:
print(list(list_of_such_numbers_pb34(bound=1000)))
print(sum_of_such_numbers_pb34(bound=1000))
[145] 145
Now let's use the bound we computed:
print(list(list_of_such_numbers_pb34(bound=max_nb_pb34)))
print(sum_of_such_numbers_pb34(bound=max_nb_pb34))
[145, 40585] 40730
import math
def erathostene_sieve(n: int) -> List[int]:
primes = [False, False] + [True] * (n - 1) # from 0 to n included
max_divisor = math.floor(math.sqrt(n))
for divisor in range(2, max_divisor + 1):
if primes[divisor]:
i = 2
while divisor * i <= n:
primes[divisor * i] = False
i += 1
return primes
sieve100 = erathostene_sieve(100)
print(sieve100)
print([i for i, b in enumerate(sieve100) if b])
[False, False, True, True, False, True, False, True, False, False, False, True, False, True, False, False, False, True, False, True, False, False, False, True, False, False, False, False, False, True, False, True, False, False, False, False, False, True, False, False, False, True, False, True, False, False, False, True, False, False, False, False, False, True, False, False, False, False, False, True, False, True, False, False, False, False, False, True, False, False, False, True, False, True, False, False, False, False, False, True, False, False, False, True, False, False, False, False, False, True, False, False, False, False, False, False, False, True, False, False, False] [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
sieve1million = erathostene_sieve(int(1e6))
print(len([i for i, b in enumerate(sieve1million) if b]))
78498
def is_prime(n: int) -> bool:
assert 1 <= n <= len(sieve1million) - 1
return sieve1million[n]
Now we have all the prime numbers up-to one million, and a constant-time way of checking if small numbers are prime.
s = '197'
print(s)
print(s[0:] + s[:0])
print(s[1:] + s[:1])
print(s[2:] + s[:2])
197 197 971 719
def circular_numbers(n: int) -> List[int]:
str_n = str(n)
return [ int(str_n[i:] + str_n[:i]) for i in range(1, len(str_n)) ]
circular_numbers(197)
[971, 719]
def is_circular_prime(n: int) -> bool:
if not is_prime(n):
return False
return all(map(is_prime, circular_numbers(n)))
print([n for n in range(2, 100) if is_circular_prime(n)])
[2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, 97]
print(len([n for n in range(2, int(1e6) + 1) if is_circular_prime(n)]))
55
It's very easy to check if $n$ is a palindrome in base $10$:
def is_palindrome_base10(n: int) -> bool:
str_n = str(n)
return str_n == str_n[::-1]
assert not is_palindrome_base10(584)
assert is_palindrome_base10(585)
assert not is_palindrome_base10(586)
It's also very easy to check if $n$ is a palindrome in base $2$:
bin(585)[2:]
'1001001001'
def is_palindrome_base2(n: int) -> bool:
str_n_2 = bin(n)[2:]
return str_n_2 == str_n_2[::-1]
assert not is_palindrome_base2(584)
assert is_palindrome_base2(585)
assert not is_palindrome_base2(586)
def palindromes_in_base10_and_base2(start: int=1, end: int=int(1e6)) -> List[int]:
return [n for n in range(start, end + 1) if is_palindrome_base10(n) and is_palindrome_base2(n)]
palindromes_in_base10_and_base2(start=1, end=1000)
[1, 3, 5, 7, 9, 33, 99, 313, 585, 717]
sum(palindromes_in_base10_and_base2(start=1, end=int(1e6)))
872187
def truncate_from_left_to_right(n: int) -> List[int]:
str_n = str(n)
return [int(str_n[i:]) for i in range(0, len(str_n))]
truncate_from_left_to_right(3797)
[3797, 797, 97, 7]
def truncate_from_right_to_left(n: int) -> List[int]:
str_n = str(n)
return [int(str_n[:i]) for i in range(len(str_n), 0, -1)]
truncate_from_right_to_left(3797)
[3797, 379, 37, 3]
def is_truncatable_prime(n: int, sieve: List[int]) -> bool:
if n <= 7 or not sieve[n]: return False
return all(sieve[k] for k in truncate_from_left_to_right(n) + truncate_from_right_to_left(n))
assert is_truncatable_prime(37, sieve=sieve1million)
assert is_truncatable_prime(3797, sieve=sieve1million)
assert not is_truncatable_prime(99371, sieve=sieve1million) # 99 is not prime!
print([i for i, b in enumerate(sieve100) if b])
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
The tricky part is to bound the largest number than can be a truncatable prime. The problem states that there are only eleven primes that are truncatable primes. How to find an upper bound on these numbers?
Well, one solution could just to enumerate prime numbers until we find 11 candidates, then stop. Let's cheat and do that.
def find_truncatable_primes(maxnumber=11, sieve=sieve1million):
truncatable_primes = []
number = 0
for i, b in enumerate(sieve1million):
if b:
if is_truncatable_prime(i, sieve=sieve):
truncatable_primes.append(i)
number += 1
if number >= maxnumber:
return truncatable_primes
find_truncatable_primes(maxnumber=11)
[23, 37, 53, 73, 313, 317, 373, 797, 3137, 3797, 739397]
find_truncatable_primes(maxnumber=12)
That's a good sign, for prime numbers up-to a million we could not find the 12th truncatable prime.
sum(find_truncatable_primes(maxnumber=11))
748317
Well I wrote the code and my notebook froze so I lost everything... Not so motivated to start again.
The first thing to do is to mathematically bound the largest integer $x$ such that the product of $x$ by $1,\dots,n$ gives a 1-to-9 pandigital number.
Let's simply try them all.
from typing import Union
def is_pandigital_1to9(n: Union[str, int]) -> bool:
str_n = str(n)
if len(str_n) != 9: return False
return sorted(str_n) == ['1','2','3','4','5','6','7','8','9']
assert is_pandigital_1to9(192384576)
assert is_pandigital_1to9(918273645)
def concatenated_product_1ton(x: int, n: int) -> str:
return ''.join(str(x * i) for i in range(1, n+1))
assert is_pandigital_1to9(concatenated_product_1ton(192, 3))
assert is_pandigital_1to9(concatenated_product_1ton(9, 5))
assert is_pandigital_1to9(concatenated_product_1ton(1, 9))
Now we can just try all the possible values for $x$ and $n$ and keep a list of the candidates and return the largest one.
def all_concatenated_product_pandigital_1to9(max_n: int):
candidates = []
for n in range(2, max_n + 1):
print(f"\nStarting with n = {n}...")
d = 1
while n*d <= 9:
print(f"\tStarting with d = {d}...")
for x in range(10**(d-1), 10**d):
prod_x_1ton = concatenated_product_1ton(x, n)
if is_pandigital_1to9(prod_x_1ton):
print(f" ==> adding {prod_x_1ton} coming from x = {x}, d = {d} and n = {n}")
candidates.append(int(prod_x_1ton))
d += 1
return candidates
def largest_concatenated_product_pandigital_1to9(max_n: int):
candidates = all_concatenated_product_pandigital_1to9(max_n = max_n)
return max(candidates)
largest_concatenated_product_pandigital_1to9(9)
Starting with n = 2... Starting with d = 1... Starting with d = 2... Starting with d = 3... Starting with d = 4... ==> adding 672913458 coming from x = 6729, d = 4 and n = 2 ==> adding 679213584 coming from x = 6792, d = 4 and n = 2 ==> adding 692713854 coming from x = 6927, d = 4 and n = 2 ==> adding 726914538 coming from x = 7269, d = 4 and n = 2 ==> adding 729314586 coming from x = 7293, d = 4 and n = 2 ==> adding 732914658 coming from x = 7329, d = 4 and n = 2 ==> adding 769215384 coming from x = 7692, d = 4 and n = 2 ==> adding 792315846 coming from x = 7923, d = 4 and n = 2 ==> adding 793215864 coming from x = 7932, d = 4 and n = 2 ==> adding 926718534 coming from x = 9267, d = 4 and n = 2 ==> adding 927318546 coming from x = 9273, d = 4 and n = 2 ==> adding 932718654 coming from x = 9327, d = 4 and n = 2 Starting with n = 3... Starting with d = 1... Starting with d = 2... Starting with d = 3... ==> adding 192384576 coming from x = 192, d = 3 and n = 3 ==> adding 219438657 coming from x = 219, d = 3 and n = 3 ==> adding 273546819 coming from x = 273, d = 3 and n = 3 ==> adding 327654981 coming from x = 327, d = 3 and n = 3 Starting with n = 4... Starting with d = 1... Starting with d = 2... Starting with n = 5... Starting with d = 1... ==> adding 918273645 coming from x = 9, d = 1 and n = 5 Starting with n = 6... Starting with d = 1... Starting with n = 7... Starting with d = 1... Starting with n = 8... Starting with d = 1... Starting with n = 9... Starting with d = 1... ==> adding 123456789 coming from x = 1, d = 1 and n = 9
932718654
If p is the perimeter of a right angle triangle with integral length sides, {a,b,c}, there are exactly three solutions for p = 120 : {20,48,52}, {24,45,51}, {30,40,50}
For which value of p ≤ 1000, is the number of solutions maximised?
Well there is no need to be smart here, we just need to try all the values $a,b,c=\sqrt{a^2+b^2}$ such that the longer slide is an integer, keep the list of values of p and the number of solutions, and find the p that maximize this number of solutions.
We could be much smarter in narrowing down the possible values for $a,b,c$, but we don't really need to.
import math
def find_all_integer_right_triangle(max_p=1000):
nb_of_solutions_by_p = {}
solutions_by_p = {}
for p in range(1, max_p + 1):
nb_of_solutions_by_p[p] = 0
solutions_by_p[p] = []
max_side = p // 2
for a in range(1, max_side + 1):
for b in range(a + 1, max_side + 1):
c = math.sqrt(a**2 + b**2)
if int(c) == c:
c = int(c)
if a + b + c == p:
nb_of_solutions_by_p[p] += 1
solutions_by_p[p] += [(a,b,c)]
max_nb_of_solutions = max(nb_solutions for nb_solutions in nb_of_solutions_by_p.values())
print(max_nb_of_solutions)
print([solutions_by_p[p] for p in solutions_by_p.keys() if nb_of_solutions_by_p[p] == max_nb_of_solutions])
return [p for p in nb_of_solutions_by_p.keys() if nb_of_solutions_by_p[p] == max_nb_of_solutions]
find_all_integer_right_triangle(max_p=120)
3 [[(20, 48, 52), (24, 45, 51), (30, 40, 50)]]
[120]
find_all_integer_right_triangle(max_p=1000)
8 [[(40, 399, 401), (56, 390, 394), (105, 360, 375), (120, 350, 370), (140, 336, 364), (168, 315, 357), (210, 280, 350), (240, 252, 348)]]
[840]
So the answer is $p=840$, which have 8 solutions.
Let's use a huge string and simply access its value at index $i=1,10,100,1000,10000,100000,1000000$. It will take some time but it'll work easily.
def construct_irrational_decimal_fraction(max_n=30):
return ''.join(str(n) for n in range(1, max_n + 1))
assert '1' == construct_irrational_decimal_fraction(30)[11] # 12th digit is 1, ok
Now we can just build a huge string and access it.
def product(iterator):
p = 1
for value in iterator:
p *= value
return p
def product_of_d1_to_dn(list_of_n=[1,10]):
max_n = max(list_of_n)
s = construct_irrational_decimal_fraction(max_n=1+max_n)
return product(int(s[di]) for di in list_of_n)
product_of_d1_to_dn([1, 11]) # product of 2th digit = 2 and 12th digit = 1 ==> 2
2
product_of_d1_to_dn([0, 9])
1
product_of_d1_to_dn([0, 9, 99])
5
product_of_d1_to_dn([0, 9, 99, 999])
15
product_of_d1_to_dn([0, 9, 99, 999, 9999])
105
product_of_d1_to_dn([0, 9, 99, 999, 9999, 99999])
210
product_of_d1_to_dn([0, 9, 99, 999, 9999, 99999, 999999])
210
If $p$ is a prime $n$-digit pandigital number, then $p$ is smaller than $n(n-1)..1$ (written in base $10$), eg with $n=5$, $p$ is smaller than $54321$. For $n=9$, the largest possible $9$-digit pandigital number is $987654321$. Let's build a Erathostene sieve of size $987654321+1$ and just check all the prime numbers for being $n$-digit pandigital, for $n=1$ to $n=9$. It won't be fast, but it'll work.
import math
from typing import List
%load_ext Cython
%%cython
import math
def erathostene_sieve(int n):
cdef list primes = [False, False] + [True] * (n - 1) # from 0 to n included
cdef int max_divisor = math.floor(math.sqrt(n))
cdef int i = 2
for divisor in range(2, max_divisor + 1):
if primes[divisor]:
number = 2*divisor
while number <= n:
primes[number] = False
number += divisor
return primes
For 10 million primes, this takes about 3 seconds on my machine, thanks to Cython being faster than naive Python code.
sieve10million = erathostene_sieve(int(1e7))
primes_upto_1e7 = [p for p,b in enumerate(sieve10million) if b]
For about 1 billion primes, this takes about 30 seconds on my machine, thanks to Cython being faster than naive Python code.
sieve1billion = erathostene_sieve(98765432+1)
For about 10 billion primes, this should take about 15 minutes on my machine, thanks to Cython being faster than naive Python code.
sieve10billion = erathostene_sieve(987654321+1)
Now we have a LARGE bool array to check in constant time if a number is prime, we can build the list of prime numbers up-to this $max_p = 987654321$.
primes_upto_987654321 = [p for p,b in enumerate(sieve10billion) if b]
del sieve1billion, sieve10billion # to save so RAM
len(primes_upto_987654321) # not so large, there is about O(log n) primes smaller than n
50251452
Let's test if a number is 1-to-n pandigital.
from typing import Union
def is_pandigital_1ton(x: Union[str, int], n: int) -> bool:
str_x = str(x)
if len(str_x) != n: return False
# fastest version
if n == 1:
return sorted(str_x) == ['1']
elif n == 2:
return sorted(str_x) == ['1','2']
elif n == 3:
return sorted(str_x) == ['1','2','3']
elif n == 4:
return sorted(str_x) == ['1','2','3','4']
elif n == 5:
return sorted(str_x) == ['1','2','3','4','5']
elif n == 6:
return sorted(str_x) == ['1','2','3','4','5','6']
elif n == 7:
return sorted(str_x) == ['1','2','3','4','5','6','7']
elif n == 8:
return sorted(str_x) == ['1','2','3','4','5','6','7','8']
elif n == 9:
return sorted(str_x) == ['1','2','3','4','5','6','7','8','9']
return False
from typing import Dict
def find_pandigital_primes(list_of_primes: List[int]) -> Dict[int, List[int]]:
solutions = dict()
for n in range(1, 9 + 1):
solutions[n] = [ prime for prime in list_of_primes if is_pandigital_1ton(prime, n) ]
return solutions
def largest_pandigital_prime(list_of_primes: List[int]) -> int:
solutions = find_pandigital_primes(list_of_primes)
# print("All 1-to-n pandigital primes:", {n: solutions[n] for n in range(1, 9 + 1)})
max_primes = { n: max(solutions[n]) for n in range(1, 9 + 1) if solutions[n] }
print("Max 1-to-n pandigital primes:", max_primes)
return max(max_primes.values())
largest_pandigital_prime(primes_upto_1e7)
Max 1-to-n pandigital primes: {4: 4231, 7: 7652413}
7652413
largest_pandigital_prime(primes_upto_987654321)
Max 1-to-n pandigital primes: {4: 4231, 7: 7652413}
7652413
!ls p042_words.txt
!wc p042_words.txt
p042_words.txt 0 1 16345 p042_words.txt
with open('p042_words.txt') as f:
words = f.readline()
words = words.replace('"','').split(',')
type(words), words[0], len(words)
(list, 'A', 1786)
Now we have these 1786 words.
max_length_words = max(map(len, words))
print(f"There is {len(words)} words, of maximum length {max_length_words}")
There is 1786 words, of maximum length 14
largest_word_value = 26 * max_length_words
print(f"So the largest word is {largest_word_value}")
So the largest word is 364
def triangle_numbers_upto(max_value: int) -> List[int]:
triangle_numbers = []
i = 1
t = i
while t <= max_value:
triangle_numbers.append(t)
i += 1
t += i
return triangle_numbers
triangles = set(triangle_numbers_upto(largest_word_value))
# using a set() will make the test n in triangles faster!
print(triangles)
print(f"There are {len(triangles)} triangle numbers smaller than {largest_word_value}")
{1, 3, 6, 136, 10, 15, 276, 21, 153, 28, 36, 171, 300, 45, 55, 190, 66, 325, 78, 210, 91, 351, 231, 105, 120, 253} There are 26 triangle numbers smaller than 364
def letter_to_int(letter: str) -> int:
return ord(letter) - ord('A') + 1
# for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
# print(f"{letter} -> {letter_to_int(letter)}")
def word_value(word: str) -> int:
return sum(letter_to_int(letter) for letter in word)
word_value("SKY") # 19 + 11 + 25 = 55
55
def is_triangle_word(word: str) -> bool:
value = word_value(word)
return value in triangles
def number_of_triangle_words(words: List[str]) -> int:
return sum(1 for word in words if is_triangle_word(word))
number_of_triangle_words(words)
162
Good, it wasn't that hard!
from typing import Union
def is_pandigital_0to9(x: Union[str, int]) -> bool:
str_x = str(x)
if len(str_x) != 10: return False
return sorted(str_x) == ['0','1','2','3','4','5','6','7','8','9']
is_pandigital_0to9(1406357289)
True
def has_substring_divisibility(x: Union[str, int]) -> bool:
x = str(x)
digits = {i+1: int(c) for i,c in enumerate(x)}
make_digit = lambda i: int(f"{digits[i]}{digits[i+1]}{digits[i+2]}")
divisors = [2, 3, 5, 7, 11, 13, 17]
return all(
make_digit(i+2) % divisor == 0
for i, divisor in enumerate(divisors)
)
assert not has_substring_divisibility(1406357287)
assert not has_substring_divisibility(1406357288)
assert has_substring_divisibility(1406357289)
assert not has_substring_divisibility(1406357290)
assert not has_substring_divisibility(1406357291)
Now we simply have to try all the 0-to-9 pandigital numbers, from 0123456789 (smallest) to 9876543210 (largest), and count the number of such numbers that satisfy this property.
It's easy to test all such pandigital numbers by using permutations of the string 0123456789, using itertools.permutations
.
import itertools
len(list(itertools.permutations(list('0123456789'))))
3628800
def find_all_substring_divisible_0to9_pandigital_numbers():
candidates = []
for digits in itertools.permutations(list('0123456789')):
str_number = ''.join(digits)
if is_pandigital_0to9(str_number):
if has_substring_divisibility(str_number):
print(f"Adding a new number = {int(str_number)}")
candidates.append(int(str_number))
return candidates
return [ int(str_number)
for str_number in itertools.permutations('0123456789')
if is_pandigital_0to9(str_number)
and has_substring_divisibility(str_number)
]
candidates = find_all_substring_divisible_0to9_pandigital_numbers()
print(f"There is {len(candidates)} such numbers, their sum = {sum(candidates)}.")
Adding a new number = 1406357289 Adding a new number = 1430952867 Adding a new number = 1460357289 Adding a new number = 4106357289 Adding a new number = 4130952867 Adding a new number = 4160357289 There is 6 such numbers, their sum = 16695334890.
def nth_pentagonal_number(n: int) -> int:
# if n is odd, 3*n-1 is even, so n * (3n-1) is even
# if n is even, n * (3n-1) is even
# so the division by two is always an integer division
return n * (3*n - 1) // 2
From the formula of $P_n = n(3n-1)/2$, it is clear that for any fixed $i$, $|P_{i+k}-P_i| = (i+k)(3(i+k)-1)/2 - i(3i-1)/2 = ( i(3i-1) + k(3i-1) + i(3k-1) + k(3k-1) - i(3i-1) )/2 = (k(3i-1) + i(3k-1) + k(3k-1))/2$ is an increasing function of $k$, and also for any fixed $k$, an increasing function of $i$.
Let's try naively to compute the first 1 million pentagonal numbers, put them in a set $\mathcal{P}$ then check if there are any $P_j,P_k$ (with $k > j$) that satisfy $P_j+P_k \in \mathcal{P}$ and $P_k - P_j \in \mathcal{P}$.
My intuition is that the solution that minimizes $D=|P_k-P_j|$ will be found within the first 1 million pentagonal numbers, so we don't need to mathematically bound the value of the largest $k,j$.
def compute_all_pentagonal_numbers(max_n: int) -> List[int]:
return [nth_pentagonal_number(n) for n in range(1, int(max_n) + 1)]
%%time
onemillion_pentagonal_numbers = compute_all_pentagonal_numbers(1e5)
CPU times: user 79.1 ms, sys: 0 ns, total: 79.1 ms Wall time: 76.4 ms
def try_all_pairs_of_pentagonal_numbers(pentagonal_numbers: List[int]) -> List[int]:
pairs_that_are_good = []
pentagonal_numbers_set = set(pentagonal_numbers)
n = len(pentagonal_numbers)
for j in range(n):
Pj = pentagonal_numbers[j]
for k in range(j + 1, n):
Pk = pentagonal_numbers[k]
their_sum = Pk + Pj
if their_sum not in pentagonal_numbers_set:
continue
their_difference = Pk - Pj
if their_difference not in pentagonal_numbers_set:
continue
print(f"Found another pair P{j} = {Pj}, P{k} = {Pk} whose sum = {their_sum} and difference = {their_difference} are pentagonal numbers too.")
pairs_that_are_good.append((their_difference, Pj, Pk, their_sum))
return pairs_that_are_good
%%time
try_all_pairs_of_pentagonal_numbers(onemillion_pentagonal_numbers)
Found another pair P1019 = 1560090, P2166 = 7042750 whose sum = 8602840 and difference = 5482660 are pentagonal numbers too.
--------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) <timed eval> in <module> <ipython-input-99-8e20aceed460> in try_all_pairs_of_pentagonal_numbers(pentagonal_numbers) 6 Pj = pentagonal_numbers[j] 7 for k in range(j + 1, n): ----> 8 Pk = pentagonal_numbers[k] 9 their_sum = Pk + Pj 10 if their_sum not in pentagonal_numbers_set: KeyboardInterrupt:
I got bored of waiting, and tadam the first solution is the good one!
Ideally, this problem will be easy to solve if there is a $\mathcal{O}(1)$ method to check if a number is a triangle, pentagonal and hexagonal number. I don't see an easy method, so let's just compute a lot of each of them, and try.
def nth_triangle_number(n: int) -> int:
return n * (n+1) // 2
def nth_pentagonal_number(n: int) -> int:
return n * (3*n - 1) // 2
def nth_hexagonal_number(n: int) -> int:
return n * (2*n - 1)
assert nth_triangle_number(285) == 40755
assert nth_pentagonal_number(165) == 40755
assert nth_hexagonal_number(143) == 40755
Now we can generate a set of a lot of triangle numbers, pentagonal numbers, hexagonal numbers, and find their intersection.
{1, 3, 6, 10, 15} & {1, 5, 12, 22, 35} & {1, 6, 15, 28, 45}
{1}
def intersection_of_sets_of_three_kinds_of_numbers(max_n: int) -> List[int]:
set_triangles = { nth_triangle_number(n) for n in range(1, max_n + 1) }
set_pentagonals = { nth_pentagonal_number(n) for n in range(1, max_n + 1) }
set_hexagonals = { nth_hexagonal_number(n) for n in range(1, max_n + 1) }
intersection = set_triangles & set_pentagonals & set_hexagonals
return sorted(list(intersection))
First, let's check that 40755=$T_{285}=P_{165}=H_{143}$ can indeed be found using this approach:
intersection_of_sets_of_three_kinds_of_numbers(300)
[1, 40755]
How can we know when to stop?
I simply tried augmenting max_n
until I found the next triangle number that is also pentagonal and hexagonal.
intersection_of_sets_of_three_kinds_of_numbers(100000)
[1, 40755, 1533776805]
%load_ext Cython
The Cython extension is already loaded. To reload it, use: %reload_ext Cython
%%cython
import math
def erathostene_sieve(int n):
cdef list primes = [False, False] + [True] * (n - 1) # from 0 to n included
cdef int max_divisor = math.floor(math.sqrt(n))
cdef int i = 2
for divisor in range(2, max_divisor + 1):
if primes[divisor]:
number = 2*divisor
while number <= n:
primes[number] = False
number += divisor
return primes
For 10 million primes, this takes about 3 seconds on my machine, thanks to Cython being faster than naive Python code.
sieve10million = erathostene_sieve(int(1e7))
primes_upto_1e7 = [p for p,b in enumerate(sieve10million) if b]
def is_integer_square(n: int) -> bool:
sqrt_n = math.sqrt(n)
return int(sqrt_n)**2 == n
assert not is_integer_square(24)
assert is_integer_square(25)
assert not is_integer_square(26)
def is_goldbach(n: int, primes: List[int], verbose: bool=True) -> bool:
# n has to be odd to be Goldbach
if n % 2 == 0:
return False
# n has to not be prime
if n in primes:
return False
# now we can test possible candidate primes
for p in primes:
if p >= n:
return False
# if n = p + 2*(a square)
remainder = n - p
if remainder % 2 != 0:
continue
candidate_square = remainder // 2
if is_integer_square(candidate_square):
i = int(math.sqrt(candidate_square))
if verbose: print(f"n = {n} was found to satisfy Goldbach's property with prime p = {p} and i = {i} (n = p + 2 i²)")
return True
return False
count = 0
max_n = 50
for n in range(1, max_n + 1):
if is_goldbach(n, primes_upto_1e7, verbose=True):
count += 1
print(f"For numbers n = 1 ... {max_n} we found {count} numbers satisfying Goldbach's conjecture.")
n = 9 was found to satisfy Goldbach's property with prime p = 7 and i = 1 (n = p + 2 i²) n = 15 was found to satisfy Goldbach's property with prime p = 7 and i = 2 (n = p + 2 i²) n = 21 was found to satisfy Goldbach's property with prime p = 3 and i = 3 (n = p + 2 i²) n = 25 was found to satisfy Goldbach's property with prime p = 7 and i = 3 (n = p + 2 i²) n = 27 was found to satisfy Goldbach's property with prime p = 19 and i = 2 (n = p + 2 i²) n = 33 was found to satisfy Goldbach's property with prime p = 31 and i = 1 (n = p + 2 i²) n = 35 was found to satisfy Goldbach's property with prime p = 3 and i = 4 (n = p + 2 i²) n = 39 was found to satisfy Goldbach's property with prime p = 7 and i = 4 (n = p + 2 i²) n = 45 was found to satisfy Goldbach's property with prime p = 13 and i = 4 (n = p + 2 i²) n = 49 was found to satisfy Goldbach's property with prime p = 17 and i = 4 (n = p + 2 i²) For numbers n = 1 ... 50 we found 10 numbers satisfying Goldbach's conjecture.
def find_smallest_odd_composite_non_Goldbach_number(primes: List[int]) -> int:
n = 7
max_n = max(primes)
while n <= max_n:
n += 2
# let's try all the odd numbers from 9
if n in primes:
continue
# we skip the primes
if not is_goldbach(n, primes, verbose=False):
return n
raise ValueError(f"Not enough prime numbers in the list (max = {max_n}), no counter example was found...")
find_smallest_odd_composite_non_Goldbach_number(primes_upto_1e7)
5777
It was that fast, about 24 seconds, but it's enough. There is surly plenty of solutions to solve this problem faster, but I don't care.
def how_many_times_x_divides_y(x: int, y: int) -> int:
c = 0
while x % y == 0:
x = x // y
c += 1
return c
assert 2 == how_many_times_x_divides_y(100, 10)
assert 2 == how_many_times_x_divides_y(644, 2)
assert 1 == how_many_times_x_divides_y(644, 7)
assert 1 == how_many_times_x_divides_y(644, 23)
assert 0 == how_many_times_x_divides_y(644, 39)
from functools import lru_cache
from typing import Tuple
@lru_cache(maxsize=None)
def number_of_distinct_prime_factors(n: int, primes: Tuple[int]) -> int:
return len([p for p in primes if p <= n and n % p == 0])
def distinct_prime_factors(n: int, primes: Tuple[int]) -> List[int]:
prime_factors = [p for p in primes if p <= n and n % p == 0]
return prime_factors
def str_prime_decomposition(n: int, primes: Tuple[int]) -> str:
prime_factors = distinct_prime_factors(n, primes)
prime_decomposition = {
p: how_many_times_x_divides_y(n, p) for p in prime_factors
}
return ' * '.join(
f"{p}**{power}" if power >= 2 else f"{p}"
for (p, power) in prime_decomposition.items()
)
primes_upto_1e7 = tuple(primes_upto_1e7)
assert 2 == number_of_distinct_prime_factors(14, primes_upto_1e7)
assert 2 == number_of_distinct_prime_factors(15, primes_upto_1e7)
for n in [14, 15]:
print(f"{n} = {str_prime_decomposition(n, primes_upto_1e7)}")
14 = 2 * 7 15 = 3 * 5
assert 3 == number_of_distinct_prime_factors(644, primes_upto_1e7)
assert 3 == number_of_distinct_prime_factors(645, primes_upto_1e7)
assert 3 == number_of_distinct_prime_factors(646, primes_upto_1e7)
for n in [644, 645, 646]:
print(f"{n} = {str_prime_decomposition(n, primes_upto_1e7)}")
644 = 2**2 * 7 * 23 645 = 3 * 5 * 43 646 = 2 * 17 * 19
Now let's find the first number to have $x$ consecutive integers each having $y$ prime factors:
def find_first_number_to_have_x_consecutive_int_with_y_prime_factors(max_n: int, x: int, y: int, primes: List[int]) -> int:
n = 1
primes = tuple([p for p in primes if y*p <= max_n])
while n <= max_n:
can_still_be_good = True
number = n
while number < n+x and can_still_be_good:
if number_of_distinct_prime_factors(number, primes) != y:
can_still_be_good = False
# we can skip a few values for n
n = number
number += 1
if can_still_be_good:
print(f"Proof that n = {n} has x = {x} consecutive integers with each y = {y} distinct prime factors:")
for i in range(n, n + x):
print(f" i = {i} has prime factors {distinct_prime_factors(i, primes)}")
print(f" Proof: i = {i} == {str_prime_decomposition(i, primes)}")
return n
n += 1
raise ValueError(f"Not enough prime numbers in the list (max = {max_n}), no counter example was found...")
Now let's try with $x=y=2$ and $x=y=3$, to check the example given in the problem statement, before trying with $x=y=4$:
find_first_number_to_have_x_consecutive_int_with_y_prime_factors(100, 2, 2, primes_upto_1e7)
Proof that n = 14 has x = 2 consecutive integers with each y = 2 distinct prime factors: i = 14 has prime factors [2, 7] Proof: i = 14 == 2 * 7 i = 15 has prime factors [3, 5] Proof: i = 15 == 3 * 5
14
find_first_number_to_have_x_consecutive_int_with_y_prime_factors(1000, 3, 3, primes_upto_1e7)
Proof that n = 644 has x = 3 consecutive integers with each y = 3 distinct prime factors: i = 644 has prime factors [2, 7, 23] Proof: i = 644 == 2**2 * 7 * 23 i = 645 has prime factors [3, 5, 43] Proof: i = 645 == 3 * 5 * 43 i = 646 has prime factors [2, 17, 19] Proof: i = 646 == 2 * 17 * 19
644
Now to answer the problem, let's compute for $x=y=4$:
%%time
find_first_number_to_have_x_consecutive_int_with_y_prime_factors(150000, 4, 4, primes_upto_1e7)
Proof that n = 134043 has x = 4 consecutive integers with each y = 4 distinct prime factors: i = 134043 has prime factors [3, 7, 13, 491] Proof: i = 134043 == 3 * 7 * 13 * 491 i = 134044 has prime factors [2, 23, 31, 47] Proof: i = 134044 == 2**2 * 23 * 31 * 47 i = 134045 has prime factors [5, 17, 19, 83] Proof: i = 134045 == 5 * 17 * 19 * 83 i = 134046 has prime factors [2, 3, 11, 677] Proof: i = 134046 == 2 * 3**2 * 11 * 677 CPU times: user 30.2 s, sys: 114 ms, total: 30.4 s Wall time: 30.4 s
134043