【问题标题】:Efficient finding primitive roots modulo n using Python?使用Python有效地找到原始根模?
【发布时间】:2016-10-22 10:07:47
【问题描述】:

我正在使用以下代码在 Python 中查找 primitive rootsn

代码:

def gcd(a,b):
    while b != 0:
        a, b = b, a % b
    return a

def primRoots(modulo):
    roots = []
    required_set = set(num for num in range (1, modulo) if gcd(num, modulo) == 1)

    for g in range(1, modulo):
        actual_set = set(pow(g, powers) % modulo for powers in range (1, modulo))
        if required_set == actual_set:
            roots.append(g)           
    return roots

if __name__ == "__main__":
    p = 17
    primitive_roots = primRoots(p)
    print(primitive_roots)

输出:

[3, 5, 6, 7, 10, 11, 12, 14]   

代码片段提取自: Diffie-Hellman (Github)


primRoots 方法能否在内存使用性能/效率方面进行简化或优化?

【问题讨论】:

  • 请注意,pow 允许使用第三个参数,即模数,这比手动应用模数要快得多。

标签: python performance python-3.x optimization simplify


【解决方案1】:

您可以在此处进行的一个快速更改(尚未有效优化)是使用列表和集合推导:

def primRoots(modulo):
    coprime_set = {num for num in range(1, modulo) if gcd(num, modulo) == 1}
    return [g for g in range(1, modulo) if coprime_set == {pow(g, powers, modulo)
            for powers in range(1, modulo)}]

现在,您可以在此处进行的一项强大而有趣的算法更改是使用 memoization 优化您的 gcd function。或者更好的是,您可以简单地使用 Python-3.5+ 中的内置 gcd 函数形式 math 模块或以前版本中的 fractions 模块:

from functools import wraps
def cache_gcd(f):
    cache = {}

    @wraps(f)
    def wrapped(a, b):
        key = (a, b)
        try:
            result = cache[key]
        except KeyError:
            result = cache[key] = f(a, b)
        return result
    return wrapped

@cache_gcd
def gcd(a,b):
    while b != 0:
        a, b = b, a % b
    return a
# or just do the following (recommended)
# from math import gcd

然后:

def primRoots(modulo):
    coprime_set = {num for num in range(1, modulo) if gcd(num, modulo) == 1}
    return [g for g in range(1, modulo) if coprime_set == {pow(g, powers, modulo)
            for powers in range(1, modulo)}]

如 cmets 中所述,作为一种更 pythoinc 的优化器方式,您可以使用 fractions.gcd(或 Python-3.5+ math.gcd)。

【讨论】:

  • @Bakuriu 确实,多么明显的失误。感谢您的注意!
  • gcd() 来自分数模块似乎同样快(用 p = 4099 测试)
  • if gcd(num, modulo) 总是正确的,也许你忘记了条件?
  • @EcirHana ==1 丢失。刚刚修好。
【解决方案2】:

根据 Pete 的评论和 Kasramvd 的回答,我可以建议:

from math import gcd as bltin_gcd

def primRoots(modulo):
    required_set = {num for num in range(1, modulo) if bltin_gcd(num, modulo) }
    return [g for g in range(1, modulo) if required_set == {pow(g, powers, modulo)
            for powers in range(1, modulo)}]

print(primRoots(17))

输出:

[3, 5, 6, 7, 10, 11, 12, 14]

变化:

  • 它现在使用 pow 方法的第三个参数来进行模数。
  • 切换到math 中定义的gcd 内置函数(用于Python 3.5)以提高速度。

关于内置 gcd 的更多信息在这里: Co-primes checking

【讨论】:

  • 请注意,gcd 至少从 python2.7 开始就在 fractions 模块中可用,可能早在之前。
【解决方案3】:

在 p 是素数的特殊情况下,下面的速度要快一些:

import sys

# translated to Python from http://www.bluetulip.org/2014/programs/primitive.js
# (some rights may remain with the author of the above javascript code)

def isNotPrime(possible):
    # We only test this here to protect people who copy and paste
    # the code without reading the first sentence of the answer.
    # In an application where you know the numbers are prime you
    # will remove this function (and the call). If you need to
    # test for primality, look for a more efficient algorithm, see
    # for example Joseph F's answer on this page.
    i = 2
    while i*i <= possible:
        if (possible % i) == 0:
            return True
        i = i + 1
    return False

def primRoots(theNum):
    if isNotPrime(theNum):
        raise ValueError("Sorry, the number must be prime.")
    o = 1
    roots = []
    r = 2
    while r < theNum:
        k = pow(r, o, theNum)
        while (k > 1):
            o = o + 1
            k = (k * r) % theNum
        if o == (theNum - 1):
            roots.append(r)
        o = 1
        r = r + 1
    return roots

print(primRoots(int(sys.argv[1])))

【讨论】:

    【解决方案4】:

    您可以通过使用更高效的算法来极大地改进您的 isNotPrime 函数。您可以通过对偶数进行特殊测试,然后只测试直到平方根的奇数来将速度提高一倍,但这与 Miller Rabin 测试等算法相比仍然非常低效。 Rosetta Code 站点中的此版本将始终对少于 25 位左右的任何数字给出正确答案。对于大素数,这将在使用试除法的一小部分时间内运行。

    此外,在本例中处理整数时,应避免使用浮点取幂运算符 **(即使我刚刚链接到的 Rosetta 代码也执行相同的操作!)。在特定情况下事情可能会正常工作,但当 Python 必须从浮点转换为整数时,或者当整数太大而无法准确地表示为浮点时,它可能是一个微妙的错误来源。您可以使用有效的整数平方根算法。这是一个简单的:

    def int_sqrt(n):
       if n == 0:
          return 0
       x = n
       y = (x + n//x)//2
    
       while (y<x):
          x=y
          y = (x + n//x)//2
    
       return x
    

    【讨论】:

    • 这不是对问题的回答,而是对我的部分回答的评论/改进。
    • Python 不会将 ** 运算符的整数转换为浮点数:比较例如2.0**1025(溢出错误)和2**1025(309 位整数结果)。
    【解决方案5】:

    这些代码都是低效的,在很多方面,首先你不需要迭代 n 的所有互质提醒,你只需要检查欧拉函数与 n 的除数的幂。在 n 是素数的情况下,欧拉函数是 n-1。如果 n i 素数,则需要分解 n-1 并仅检查那些除法器,而不是全部。这背后有一个简单的数学原理。

    第二。您需要更好的函数来为数字提供动力,假设功率太大,我认为在 python 中,您有函数 pow(g, powers, modulo) 在每个步骤中进行除法并仅获得余数(_ % modulo)。

    如果您要实现 Diff & Helman 算法,最好使用安全素数。它们是这样的素数,即 p 是素数,而 2p+1 也是素数,因此 2p+1 称为安全素数。如果你得到 n = 2*p+1,那么那个 n-1(n 是素数,欧拉函数从 n 是 n-1)的除数是 1、2、p 和 2p,你只需要检查数字是否g 的 2 次方和 g 的 p 次方,如果其中一个给出 1,那么那个 g 不是原根,你可以扔掉那个 g 并选择另一个 g,下一个 g+1,如果 g^2 和 g^ p 不等于 1,以 n 为模,则 g 是一个原始根,该检查保证,除 2p 之外的所有幂将给出不同于 1 的以 n 为模的数字。

    示例代码使用 Sophie Germain 素数 p 和相应的安全素数 2p+1,并计算该安全素数 2p+1 的本原根。

    您可以轻松地为任何素数或任何其他数字重新编写代码,方法是添加一个函数来计算欧拉函数并找到该值的所有除数。但这只是一个演示而不是完整的代码。而且可能有更好的方法。

    class SGPrime :
        '''
        This object expects a Sophie Germain prime p, it does not check that it accept that as input.
        Euler function from any prime is n-1, and the order (see method get_order) of any co-prime 
        remainder of n could be only a divider of Euler function value.  
        '''
        def __init__(self, pSophieGermain ):
            self.n = 2*pSophieGermain+1
            #TODO! check if pSophieGermain is prime 
            #TODO! check if n is also prime.
            #They both have to be primes, elsewhere the code does not work!
    
            # Euler's function is n-1, #TODO for any n, calculate Euler's function from n
            self.elrfunc = self.n-1
        
            # All divisors of Euler's function value, #TODO for any n, get all divisors of the Euler's function value.
            self.elrfunc_divisors = [1, 2, pSophieGermain, self.elrfunc]
        
            
        def get_order(self, r):
            ''' 
            Calculate the order of a number, the minimal power at which r would be congruent with 1 by modulo p.
            '''
            r = r % self.n
            for d in self.elrfunc_divisors:
               if ( pow( r, d, self.n) == 1 ):
                   return d
            return 0 # no such order, not possible if n is prime, - see small Fermat's theorem
        
        def is_primitive_root(self, r):
            '''
            Check if r is a primitive root by modulo p. Such always exists if p is prime.
            '''
            return ( self.get_order(r) == self.elrfunc )
        
        def find_all_primitive_roots(self, max_num_of_roots = None):
            '''
            Find all primitive roots, only for demo if n is large the list is large for DH or any other such algorithm 
            better to stop at first primitive roots.
            '''
            primitive_roots = []
            for g in range(1, self.n):
                if ( self.is_primitive_root(g) ):
                    primitive_roots.append(g)
                    if (( max_num_of_roots != None ) and (len(primitive_roots) >= max_num_of_roots)):
                        break
            return primitive_roots
    
    #demo, Sophie Germain's prime
    p =  20963
    sggen = SGPrime(p)
    print (f"Safe prime : {sggen.n}, and primitive roots of {sggen.n} are : " )
    print(sggen.find_all_primitive_roots())
    

    问候

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-08
      • 1970-01-01
      • 1970-01-01
      • 2021-06-26
      相关资源
      最近更新 更多