Here's Project Euler problem 451:
Consider the number \$15\$. There are eight positive numbers less than \$15\$ which are coprime to \$15\$: \begin{align*} 1, 2, 4, 7, 8, 11, 13, 14. \end{align*} The modular inverses of these numbers modulo \$15\$ are: \begin{align*} 1, 8, 4, 13, 2, 11, 7, 14 \end{align*} because
\begin{align*} 1 \times 1 \mod 15 &= 1 \\ 2 \times 8 = 16 \mod 15 &= 1 \\ 4 \times 4=16 \mod 15 &= 1 \\ 7 \times 13=91 \mod 15 &= 1 \\ 11 \times 11=121 \mod 15 &= 1 \\ 14 \times 14=196 \mod 15 &= 1 \end{align*}
Let \$I(n)\$ be the largest positive number \$m\$ smaller than \$n − 1\$ such that the modular inverse of \$m\$ modulo \$n\$ equals \$m\$ itself. So \$I(15) = 11\$. Also \$I(100) = 51\$ and \$I(7) = 1\$.
Find \$\sum I(n)\$ for \$3 \leq n \leq 2 \times 10^7\$.
I realised that because they are symmetrical, we can search for the smallest ones (of course skipping the trivial case \$1^2 = 1 \mod n\$). I also realised that if \$n\$ is divisible by a prime, I don't need to test multiples of that prime. I currently devised a implementation for the first four primes, as I think they are the ones that have the most impact. Anyways, here's my code. However, I must warn you, that in trying to optimise for speed, this one is very memory inefficient (needs around 2.5 GB).
upto=2*10**7+1
a = [True] * upto
p = []
for n in range(2,upto):
if a[n]:
p.append(n)
for k in range(2,(upto+n-1)//n):
a[k*n] = False
p=set(p)
su=0
squ=list(map(lambda x: x*x, range(upto)))
print('primes and squares ready')
s2 = set(range(0,upto,2))# for testing divisibility
s3 = set(range(0,upto,3))
s5 = set(range(0,upto,5))
s7 = set(range(0,upto,7))
r=[]
r.append([])
r.append([])
r[0].append([])
r[0].append([])
r[1].append([])
r[1].append([])
r[0][0].append([])
r[0][0].append([])
r[0][1].append([])
r[0][1].append([])
r[1][0].append([])
r[1][0].append([])
r[1][1].append([])
r[1][1].append([])
r[0][0][0].append(range(2,upto))# for iterating
r[1][0][0].append(filter(lambda x: x not in s2, range(upto)))
r[0][1][0].append(filter(lambda x: x not in s3, range(upto)))
r[0][0][1].append(filter(lambda x: x not in s5, range(upto)))
r[0][0][0].append(filter(lambda x: x not in s7, range(upto)))
print('list pre generated')
r[1][1][0].append(filter(lambda x: x in r[1][0][0][0], r[0][1][0][0]))
r[1][1][1].append(filter(lambda x: x in r[0][0][1][0], r[1][1][0][0]))
r[0][1][1].append(filter(lambda x: x in r[0][0][1][0], r[0][1][0][0]))
r[1][0][1].append(filter(lambda x: x in r[0][0][1][0], r[1][0][0][0]))
r[1][0][1].append(filter(lambda x: x in r[1][0][1][0], r[0][0][0][1]))
r[1][0][0].append(filter(lambda x: x in r[1][0][0][0], r[0][0][0][1]))
r[1][1][0].append(filter(lambda x: x in r[1][1][0][0], r[0][0][0][1]))
r[1][1][1].append(filter(lambda x: x in r[0][0][0][1], r[1][1][1][0]))
r[0][1][1].append(filter(lambda x: x in r[0][0][0][1], r[0][1][1][0]))
r[0][0][1].append(filter(lambda x: x in r[0][0][1][0], r[0][0][0][1]))
r[0][1][0].append(filter(lambda x: x in r[0][1][0][0], r[0][0][0][1]))
print('set up complete')
for n in range(3,upto):
if n in p:
su+=1
print('testing', n)
else:
if n in s2:
d2=1
else:
d2=0
if n in s3:
d3=1
else:
d3=0
if n in s5:
d5=1
else:
d5=0
if n in s7:
d7=1
else:
d7=0
for x in r[d2][d3][d5][d7]:
if squ[x]%n==1:
su+=n-x
break
print(su)
I have tried optimising the speed of division testing using sets, and pre-generated all the generators or lists (I wasn't sure about the performance in this case, so I tried both). I must say the code runs fast at first, checking the first 100,000 moduli in under a minute, however close to a million is slow substantially. Although cProfile
doesn't time built-in functions I believe the majority of time is spent in the for x in r[d2][d3][d5][d7]:
loop.
I'm not sure if the algorithm I'm using is wrong and faster ones exist, or if my code is simply inefficient (Project Euler claims the problem can be computed in under a minute, not sure if this is the case with Python though).
As this is a project to benefit my understanding of programming, and algorithms, please don't post full solutions but rather guidance and pointers.