简体   繁体   中英

Scope of lists in Python - Project Euler 007

first question here. I am trying to learn python by stepping through project euler, and I've run into a roadblock. The following method (returns a list of prime factors) works fine for a single call:

def findPrimeFactors(num, primeFactors = []):
    '''Find the prime factors of an arbitrary positive integer

        input: num to factorize
        returns: a list containing the prime factors of the number
    '''
    pIndex = 2

    while (num >= pIndex):
        if num % pIndex == 0:
            num /= pIndex
            primeFactors.append(pIndex)
            return FindPrimes.findPrimeFactors(num, primeFactors)

        else:
            pIndex += 1

    return primeFactors

However when I use it in a loop, like so (this method may not be complete yet, currently results in infinite loop since more primes cannot be found):

def countPrimes(n = 1001):
    '''find n amount of unique primes ascending

        input: number of primes to find
        returns: list of n primes starting from 2   '''

    primes = []
    i = 2

    while len(primes) < n:
        primeFactors = FindPrimes.findPrimeFactors(i)
        print(primeFactors) #verify method behavior

        if len(primeFactors) is 1:
            primes.append(primeFactors[0])   
        i += 1

    return primes

The result is that the first loop returns [2], the next returns [2, 3], and so on, appending the new results to the list that I Wanted to have been empty on the first recursive call. It seems that my list is persisting, but I'm not sure exactly why? I read Python Class scope & lists as well which gives me some clues but the recursion complicates it more.

Recursive also means I cannot simply assign an empty set to it either. Coming from a C++ background, my expectation was that the primeFactors variable should be reinitialized each time the function is called from my program. Still a baby snake here.

EDIT: This is the iterative version of findPrimeFactors I wrote. I know it is not optimal - but I would like to at least make it efficient enough to meet Project Euler's 1 minute rule. Any suggestions for improvement or clarity are appreciated.

PRIMES = [2,3,5,7,11,13,17,19]
import math

class FindPrimes():

    '''V2 iterative'''
    def findPrimeFactors(n, primeFactors = None):
        '''Find the prime factors of an arbitrary positive integer

            input: num to factorize
            returns: a list containing the prime factors of the number
        '''

        if primeFactors is None:
            primeFactors = []

        num = n
        ceil = math.sqrt(n) #currently unused

        global PRIMES
        knownPrimes = PRIMES

        #check known primes for divisors first, then continue searching for primes by brute force
        while True:

            factorFound = False
            for prime in knownPrimes:   

                if num % prime == 0:
                    primeFactors.append(prime)
                    num /= prime
                    factorFound = True
                    break       #ensure that the list returned has ascending primes

            if not factorFound:
                break

        #once attempts have been made to reduce using known primes
        #search for new primes if the number is not fully reduced

        i = knownPrimes[-1] + 2

        while num != 1:

            if num % i == 0:
                knownPrimes.append(i)
                primeFactors.append(i)
                num /= i

            i += 2          

        return primeFactors


    def countPrimes(n = 10001):
        '''find n amount of unique primes ascending

            input: number of primes to find
            returns: list of n primes starting from 2   '''

        primes = []
        i = 2

        while len(primes) < n:

            primeFactors = FindPrimes.findPrimeFactors(i)

            if len(primeFactors) == 1:
                primes.append(primeFactors[0])
                #print(primeFactors[-1])

            i += 1

        print(len(primes))
        return primes

nth = 10001
print(FindPrimes.countPrimes(nth)[nth-1])   #print the largest prime found

The default value of primeFactors is being shared between calls, so when you change it, it stays changed for future calls.

Example:

def foo(bar = []):
    bar.append(1)
    return bar

print foo()
print foo()

Output:

[1]
[1, 1]

You should return a new list instead of changing the default:

def foo(bar = []):
    return bar + [1]

print foo()
print foo()

Output:

[1]
[1]

As mentioned by hammar, the default value is only created once, when the function is defined, and shared between calls.

The usual way around that is to use a marker value as the default:

def findPrimeFactors(num, primeFactors=None):
    if primeFactors is None:
        primeFactors = []
    ...

Off-topic, but your function findPrimeFactor() will recurse once for every prime factor found. Python doesn't do tail call removal, so you should probably rewrite this using iteration instead of recursion.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM