Skip to content

Commit 590e47b

Browse files
Add solution for problem 810
1 parent ad1e62a commit 590e47b

File tree

1 file changed

+77
-78
lines changed

1 file changed

+77
-78
lines changed

project_euler/problem_810/sol1.py

Lines changed: 77 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -20,133 +20,132 @@
2020
Similarly, 5 = 3 ⊗ 3 is not an XOR-prime.
2121
The first few XOR-primes are 2, 3, 7, 11, 13, ... and the 10th XOR-prime is 41.
2222
23-
Find the 5,000,000.th XOR-prime.
23+
Find the 5,000,000 th XOR-prime.
2424
2525
References:
2626
http://en.wikipedia.org/wiki/M%C3%B6bius_function
2727
2828
"""
2929

30-
from array import array
30+
import math
3131

3232

33-
def xor_multiply(op_a: int, op_b: int) -> int:
33+
def get_divisors(num: int) -> set[int]:
34+
"""
35+
Return all positive divisors of num.
36+
>>> get_divisors(12)
37+
{1, 2, 3, 4, 6, 12}
3438
"""
35-
Perform XOR multiplication of two integers, equivalent to polynomial
36-
multiplication in GF(2).
39+
divisors = {1}
40+
for i in range(2, int(math.sqrt(num)) + 1):
41+
if num % i == 0:
42+
divisors.add(i)
43+
divisors.add(num // i)
44+
divisors.add(num)
45+
return divisors
46+
3747

38-
>>> xor_multiply(3, 5) # (011) ⊗ (101)
48+
def xor_multiply(op_a: int, op_b: int) -> int:
49+
"""
50+
Perform XOR-based multiplication (polynomial multiplication mod 2).
51+
>>> xor_multiply(3, 5)
3952
15
4053
"""
41-
res = 0
54+
result = 0
4255
while op_b:
4356
if op_b & 1:
44-
res ^= op_a
57+
result ^= op_a
4558
op_a <<= 1
4659
op_b >>= 1
47-
return res
48-
49-
50-
def divisors(num: int) -> set[int]:
51-
"""
52-
Return all divisors of `num` (excluding 0).
53-
54-
>>> divisors(12)
55-
{1, 2, 3, 4, 6, 12}
56-
"""
57-
s = {1}
58-
for i in range(2, int(num**0.5) + 1):
59-
if num % i == 0:
60-
s.add(i)
61-
s.add(num // i)
62-
s.add(num)
63-
return s
60+
return result
6461

6562

66-
def mobius_table(num: int) -> list[int]:
63+
def mobius_table(lim: int, k: int = 2) -> list[int]:
6764
"""
68-
Generate a variant of Möbius function values from 1 to num.
69-
65+
Compute a modified Mobius function table up to `lim`.
7066
>>> mobius_table(10)[:6]
7167
[0, 1, -1, -1, 0, -1]
7268
"""
73-
mob = [1] * (num + 1)
74-
is_prime = [True] * (num + 1)
75-
mob[0] = 0
69+
mob = [0] + [1] * lim
70+
is_prime = [True] * (lim + 1)
71+
is_prime[0] = is_prime[1] = False
7672

77-
for p in range(2, num + 1):
73+
for p in range(2, lim + 1):
7874
if is_prime[p]:
79-
mob[p] = -1
80-
for j in range(2 * p, num + 1, p):
81-
is_prime[j] = False
82-
mob[j] *= -1
83-
p2 = p * p
84-
if p2 <= num:
85-
for j in range(p2, num + 1, p2):
86-
mob[j] = 0
75+
mob[p] *= -1
76+
for mul in range(2 * p, lim + 1, p):
77+
is_prime[mul] = False
78+
mob[mul] *= -1
79+
80+
p_pow = p**k
81+
if p_pow <= lim:
82+
for mul in range(p_pow, lim + 1, p_pow):
83+
mob[mul] = 0
8784
return mob
8885

8986

90-
def count_irreducibles(deg: int) -> int:
87+
def cnt_irred_pol(num: int) -> int:
9188
"""
92-
Count the number of irreducible polynomials of degree `deg` over GF(2)
93-
using the variant of Möbius function.
94-
95-
>>> count_irreducibles(3)
89+
Return the number of monic irreducible polynomials of degree num over GF(2).
90+
>>> cnt_irred_pol(3)
9691
2
9792
"""
98-
mob = mobius_table(deg)
99-
total = 0
100-
for div in divisors(deg) | {deg}:
101-
total += mob[div] * (1 << (deg // div))
102-
return total // deg
93+
mob = mobius_table(num)
94+
total = sum(mob[d] * (2 ** (num // d)) for d in get_divisors(num))
95+
return total // num
10396

10497

105-
def find_xor_prime(rank: int) -> int:
98+
def xor_prime_func(tgt_idx: int) -> int:
10699
"""
107-
Find the Nth XOR prime using a bitarray-based sieve.
108-
109-
>>> find_xor_prime(10)
100+
Find the N-th XOR-prime (irreducible polynomial) index approximation.
101+
>>> xor_prime_func(10)
110102
41
111103
"""
112104
total, degree = 0, 1
113-
while total + count_irreducibles(degree) < rank:
114-
total += count_irreducibles(degree)
105+
106+
while True:
107+
cnt = cnt_irred_pol(degree)
108+
if total + cnt > tgt_idx:
109+
break
110+
total += cnt
115111
degree += 1
116112

117-
limit = 1 << (degree + 1)
113+
lim = 1 << (degree + 1)
114+
is_prime = [True] * lim
115+
is_prime[0] = is_prime[1] = False
118116

119-
sieve = array("B", [1]) * limit
120-
sieve[0] = sieve[1] = 0
117+
for even in range(4, lim, 2):
118+
is_prime[even] = False
121119

122-
current = 0
123-
for i in range(2, limit):
124-
if sieve[i]:
125-
current += 1
126-
if current == rank:
127-
return i
120+
cnt = 1
121+
for num in range(3, lim, 2):
122+
if not is_prime[num]:
123+
continue
128124

129-
j = i
130-
while True:
131-
prod = xor_multiply(i, j)
132-
if prod >= limit:
133-
break
134-
sieve[prod] = 0
135-
j += 1
125+
cnt += 1
126+
if cnt == tgt_idx:
127+
return num
136128

137-
raise ValueError("Failed to locate the requested XOR-prime")
129+
mul = num
130+
while True:
131+
prod = xor_multiply(mul, num)
132+
if prod >= lim:
133+
break
134+
is_prime[prod] = False
135+
mul += 2
138136

137+
raise ValueError("Could not compute the XOR-prime.")
139138

140-
def solution(limit: int = 5000001) -> int:
141-
"""
142-
Wrapper for Project Euler-style solution function.
143139

140+
def solution(nth: int = 5000000) -> int:
141+
"""
142+
Compute the Nth XOR prime and print timing.
144143
>>> solution(10)
145144
41
146145
"""
147-
result = find_xor_prime(limit)
146+
result = xor_prime_func(nth)
148147
return result
149148

150149

151150
if __name__ == "__main__":
152-
print(f"{solution(5000000) = }")
151+
print(f"{solution() = }")

0 commit comments

Comments
 (0)