|
20 | 20 | Similarly, 5 = 3 ⊗ 3 is not an XOR-prime. |
21 | 21 | The first few XOR-primes are 2, 3, 7, 11, 13, ... and the 10th XOR-prime is 41. |
22 | 22 |
|
23 | | -Find the 5,000,000.th XOR-prime. |
| 23 | +Find the 5,000,000 th XOR-prime. |
24 | 24 |
|
25 | 25 | References: |
26 | 26 | http://en.wikipedia.org/wiki/M%C3%B6bius_function |
27 | 27 |
|
28 | 28 | """ |
29 | 29 |
|
30 | | -from array import array |
| 30 | +import math |
31 | 31 |
|
32 | 32 |
|
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} |
34 | 38 | """ |
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 | + |
37 | 47 |
|
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) |
39 | 52 | 15 |
40 | 53 | """ |
41 | | - res = 0 |
| 54 | + result = 0 |
42 | 55 | while op_b: |
43 | 56 | if op_b & 1: |
44 | | - res ^= op_a |
| 57 | + result ^= op_a |
45 | 58 | op_a <<= 1 |
46 | 59 | 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 |
64 | 61 |
|
65 | 62 |
|
66 | | -def mobius_table(num: int) -> list[int]: |
| 63 | +def mobius_table(lim: int, k: int = 2) -> list[int]: |
67 | 64 | """ |
68 | | - Generate a variant of Möbius function values from 1 to num. |
69 | | -
|
| 65 | + Compute a modified Mobius function table up to `lim`. |
70 | 66 | >>> mobius_table(10)[:6] |
71 | 67 | [0, 1, -1, -1, 0, -1] |
72 | 68 | """ |
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 |
76 | 72 |
|
77 | | - for p in range(2, num + 1): |
| 73 | + for p in range(2, lim + 1): |
78 | 74 | 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 |
87 | 84 | return mob |
88 | 85 |
|
89 | 86 |
|
90 | | -def count_irreducibles(deg: int) -> int: |
| 87 | +def cnt_irred_pol(num: int) -> int: |
91 | 88 | """ |
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) |
96 | 91 | 2 |
97 | 92 | """ |
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 |
103 | 96 |
|
104 | 97 |
|
105 | | -def find_xor_prime(rank: int) -> int: |
| 98 | +def xor_prime_func(tgt_idx: int) -> int: |
106 | 99 | """ |
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) |
110 | 102 | 41 |
111 | 103 | """ |
112 | 104 | 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 |
115 | 111 | degree += 1 |
116 | 112 |
|
117 | | - limit = 1 << (degree + 1) |
| 113 | + lim = 1 << (degree + 1) |
| 114 | + is_prime = [True] * lim |
| 115 | + is_prime[0] = is_prime[1] = False |
118 | 116 |
|
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 |
121 | 119 |
|
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 |
128 | 124 |
|
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 |
136 | 128 |
|
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 |
138 | 136 |
|
| 137 | + raise ValueError("Could not compute the XOR-prime.") |
139 | 138 |
|
140 | | -def solution(limit: int = 5000001) -> int: |
141 | | - """ |
142 | | - Wrapper for Project Euler-style solution function. |
143 | 139 |
|
| 140 | +def solution(nth: int = 5000000) -> int: |
| 141 | + """ |
| 142 | + Compute the Nth XOR prime and print timing. |
144 | 143 | >>> solution(10) |
145 | 144 | 41 |
146 | 145 | """ |
147 | | - result = find_xor_prime(limit) |
| 146 | + result = xor_prime_func(nth) |
148 | 147 | return result |
149 | 148 |
|
150 | 149 |
|
151 | 150 | if __name__ == "__main__": |
152 | | - print(f"{solution(5000000) = }") |
| 151 | + print(f"{solution() = }") |
0 commit comments