How to make perfect power algorithm more efficient?

后端 未结 4 632
长发绾君心
长发绾君心 2021-01-26 02:31

I have the following code:

def isPP(n):
  pos = [int(i) for i in range(n+1)]
  pos = pos[2:] ##to ignore the trivial n** 1 == n case
  y = []
  for i in pos:
            


        
相关标签:
4条回答
  • 2021-01-26 02:43

    A number n is a perfect power if there exists a b and e for which b^e = n. For instance 216 = 6^3 = 2^3 * 3^3 is a perfect power, but 72 = 2^3 * 3^2 is not.

    The trick to determining if a number is a perfect power is to know that, if the number is a perfect power, then the exponent e must be less than log2 n, because if e is greater then 2^e will be greater than n. Further, it is only necessary to test prime es, because if a number is a perfect power to a composite exponent it will also be a perfect power to the prime factors of the composite component; for instance, 2^15 = 32768 = 32^3 = 8^5 is a perfect cube root and also a perfect fifth root.

    The function isPerfectPower shown below tests each prime less than log2 n by first computing the integer root using Newton's method, then powering the result to check if it is equal to n. Auxiliary function primes compute a list of prime numbers by the Sieve of Eratosthenes, iroot computes the integer kth-root by Newton's method, and ilog computes the integer logarithm to base b by binary search.

    def primes(n): # sieve of eratosthenes
        i, p, ps, m = 0, 3, [2], n // 2
        sieve = [True] * m
        while p <= n:
            if sieve[i]:
                ps.append(p)
                for j in range((p*p-3)/2, m, p):
                    sieve[j] = False
            i, p = i+1, p+2
        return ps
    
    def iroot(k, n): # assume n > 0
        u, s, k1 = n, n+1, k-1
        while u < s:
            s = u
            u = (k1 * u + n // u ** k1) // k
        return s
    
    def ilog(b, n): # max e where b**e <= n
        lo, blo, hi, bhi = 0, 1, 1, b
        while bhi < n:
            lo, blo, hi, bhi = hi, bhi, hi+hi, bhi*bhi
        while 1 < (hi - lo):
            mid = (lo + hi) // 2
            bmid = blo * pow(b, (mid - lo))
            if n < bmid: hi, bhi = mid, bmid
            elif bmid < n: lo, blo = mid, bmid
            else: return mid
        if bhi == n: return hi
        return lo
    
    def isPerfectPower(n): # x if n == x ** y, or False
        for p in primes(ilog(2,n)):
            x = iroot(p, n)
            if pow(x, p) == n: return x
        return False
    

    There is further discussion of the perfect power predicate at my blog.

    0 讨论(0)
  • 2021-01-26 02:48

    a relevant improvement would be:

    import math
    
    def isPP(n):
    
            # first have a look at the length of n in binary representation
            ln = int(math.log(n)/math.log(2)) + 1
    
            y = []
            for i in range(n+1):
                    if (i <= 1):
                            continue
                    # calculate max power
    
                    li = int(math.log(i)/math.log(2))
                    mxi = ln / li + 1
                    for it in range(mxi):
                            if (it <= 1):
                                    continue
                            if i ** it == n:
                                    y.append((i,it))
                                    # break if you only need 1
    
            if len(y) <1:
                    return None
            else:
                    return list(y[0])
    
    0 讨论(0)
  • 2021-01-26 02:50

    IIRC, it's far easier to iteratively check "Does it have a square root? Does it have a cube root? Does it have a fourth root? ..." You will very quickly get to the point where putative roots have to be between 1 and 2, at which point you can stop.

    0 讨论(0)
  • 2021-01-26 02:57

    I think a better way would be implementing this "hack":

    import math
    
    def isPP(n):
        range = math.log(n)/math.log(2)
        range = (int)(range)
        result = []
        for i in xrange(n):
            if(i<=1):
                continue
            exponent = (int)(math.log(n)/math.log(i))
            for j in [exponent-1, exponent, exponent+1]:
                if i ** j == n:
                    result.append([i,j])
        return result
    
    print isPP(10000)
    

    Result:

    [[10,4],[100,2]]
    

    The hack uses the fact that:

    if log(a)/log(b) = c,
        then power(b,c) = a
    

    Since this calculation can be a bit off in floating points giving really approximate results, exponent is checked to the accuracy of +/- 1.

    You can make necessary adjustments for handling corner cases like n=1, etc.

    0 讨论(0)
提交回复
热议问题