207 lines
4.9 KiB
Python
207 lines
4.9 KiB
Python
"""
|
||
The number, 179, is called a circular prime because all rotations of the digits: 179, 791, and 917,
|
||
are themselves prime.
|
||
|
||
There are thirteen such primes below 100 : 2,3,5,7,11,13,17,31,37,71,73,79, and 97.
|
||
|
||
How many circular primes are there below one million?
|
||
"""
|
||
|
||
import random
|
||
import time
|
||
from itertools import permutations
|
||
from sqlite3.dbapi2 import Time
|
||
from typing import Union
|
||
|
||
# 预计算的小素数集合,用于快速排除
|
||
_SMALL_PRIMES = frozenset((2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37))
|
||
|
||
|
||
def timer(func):
|
||
def wrapper(*args, **kwargs):
|
||
start_time = time.time()
|
||
result = func(*args, **kwargs)
|
||
end_time = time.time()
|
||
print(f"{func.__name__} time: {end_time - start_time:.6f} seconds")
|
||
return result
|
||
|
||
return wrapper
|
||
|
||
|
||
def sieve_best(n: int) -> list[int]:
|
||
"""返回 [2..n) 内所有素数"""
|
||
if n <= 2:
|
||
return []
|
||
size = (n - 1) // 2
|
||
comp = bytearray(size)
|
||
limit = int(n**0.5) // 2
|
||
for i in range(1, limit + 1):
|
||
if not comp[i]:
|
||
p = 2 * i + 1
|
||
s = (p * p - 1) // 2
|
||
comp[s::p] = b"\1" * ((size - s - 1) // p + 1)
|
||
res = [2, *(2 * i + 1 for i, v in enumerate(comp) if not v)]
|
||
res.sort()
|
||
return [i for i in res if i != 1]
|
||
|
||
|
||
def is_circular(n: int, primes: list[int]) -> bool:
|
||
if n < 2:
|
||
return False
|
||
if n in [2, 3, 5, 7, 11]:
|
||
return True
|
||
digits = str(n)
|
||
allnum = [digits[i:] + digits[:i] for i in range(len(digits))]
|
||
for num in allnum:
|
||
if int(num) not in primes:
|
||
return False
|
||
return True
|
||
|
||
|
||
def is_probable_prime(n: Union[int, str], k: int = 20) -> bool:
|
||
"""
|
||
Miller-Rabin素性检测算法
|
||
|
||
参数:
|
||
n: 待检测的整数(支持int或数字字符串)
|
||
k: 测试次数,误差率约为4^(-k),默认20次(误差率约9e-13)
|
||
|
||
返回:
|
||
bool: 如果n很可能是素数返回True,否则返回False
|
||
"""
|
||
# 支持字符串输入
|
||
if isinstance(n, str):
|
||
n = int(n)
|
||
|
||
# 基本检查
|
||
if n < 2:
|
||
return False
|
||
if n in _SMALL_PRIMES:
|
||
return True
|
||
|
||
# 检查小素数的倍数
|
||
for p in _SMALL_PRIMES:
|
||
if n % p == 0:
|
||
return False
|
||
|
||
# 特殊情况:偶数(已排除2)
|
||
if n % 2 == 0:
|
||
return False
|
||
|
||
# 将 n-1 写成 2^s * d 的形式
|
||
d = n - 1
|
||
s = 0
|
||
while d % 2 == 0:
|
||
d //= 2
|
||
s += 1
|
||
|
||
# 边界:如果 n-1 可以直接作为基数,避免随机数生成问题
|
||
if n < 4:
|
||
return n in (2, 3)
|
||
|
||
# Miller-Rabin测试
|
||
for _ in range(k):
|
||
# 生成随机基数,范围 [2, n-2]
|
||
a = random.randrange(2, n - 1)
|
||
|
||
# 计算 x = a^d mod n
|
||
x = pow(a, d, n)
|
||
|
||
# 如果 x == 1 或 x == n-1,通过本轮测试
|
||
if x == 1 or x == n - 1:
|
||
continue
|
||
|
||
# 重复平方检查
|
||
for _ in range(s - 1):
|
||
x = pow(x, 2, n)
|
||
if x == n - 1:
|
||
break
|
||
else:
|
||
# 所有平方后都没有得到 n-1,n是合数
|
||
return False
|
||
|
||
return True
|
||
|
||
|
||
# 优化的确定素数版本(适用于小整数)
|
||
def is_prime_small(n: int) -> bool:
|
||
"""确定性素数检测,适用于n < 2^64"""
|
||
if n < 2:
|
||
return False
|
||
if n % 2 == 0:
|
||
return n == 2
|
||
if n % 3 == 0:
|
||
return n == 3
|
||
|
||
# 检查小素数
|
||
for p in _SMALL_PRIMES:
|
||
if n % p == 0:
|
||
return n == p
|
||
|
||
# 6k±1 优化检查
|
||
i = 5
|
||
w = 2
|
||
while i * i <= n:
|
||
if n % i == 0:
|
||
return False
|
||
i += w
|
||
w = 6 - w # 在2和4之间切换:5,7,11,13,17,19...
|
||
|
||
return True
|
||
|
||
|
||
# 智能选择函数
|
||
def is_prime(n: Union[int, str], k: int = 20) -> bool:
|
||
"""
|
||
智能素数检测:对小整数使用确定性算法,对大整数使用Miller-Rabin
|
||
|
||
参数:
|
||
n: 待检测的整数
|
||
k: Miller-Rabin测试次数(仅对大整数有效)
|
||
|
||
返回:
|
||
bool: 如果是素数返回True
|
||
"""
|
||
if isinstance(n, str):
|
||
n = int(n)
|
||
|
||
# 小整数使用确定性算法
|
||
if n < 2**64:
|
||
return is_prime_small(n)
|
||
|
||
# 大整数使用Miller-Rabin
|
||
return is_probable_prime(n, k)
|
||
|
||
|
||
@timer
|
||
def main_quick(max: int) -> None:
|
||
res = set()
|
||
for i in range(2, max):
|
||
if is_prime(i):
|
||
if i in res:
|
||
continue
|
||
else:
|
||
s = str(i)
|
||
allnums = [s[i:] + s[:i] for i in range(len(s))]
|
||
flag = 0
|
||
for j in allnums:
|
||
num = int("".join(j))
|
||
if is_prime(num):
|
||
flag += 1
|
||
if flag == len(allnums):
|
||
res.update(allnums)
|
||
print(len(res))
|
||
|
||
|
||
@timer
|
||
def main(n: int) -> None:
|
||
primes = sieve_best(n)
|
||
circular_primes = [p for p in primes if is_circular(p, primes)]
|
||
print(len(circular_primes))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
num = 1000000
|
||
main(num)
|
||
main_quick(num)
|