Обхват на списъците в Python - Project Euler 007

първия въпрос тук. Опитвам се да науча Python, като преминавам през проекта euler, и се натъкнах на пречка. Следният метод (връща списък с прости множители) работи добре за едно извикване:

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

Въпреки това, когато го използвам в цикъл, така (този метод може все още да не е завършен, в момента води до безкраен цикъл, тъй като не могат да бъдат намерени повече прости числа):

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

Резултатът е, че първият цикъл връща [2], следващият връща [2, 3] и т.н., добавяйки новите резултати към списъка, който исках да е празен при първото рекурсивно повикване. Изглежда, че списъкът ми продължава, но не съм сигурен точно защо? Прочетох и Python Class scope & lists, което ми дава някои улики, но рекурсията го усложнява още повече .

Рекурсивно също означава, че не мога просто да му присвоя празен набор. Идвайки от фона на C++, очакванията ми бяха, че променливата primeFactors трябва да се инициализира отново всеки път, когато функцията се извиква от моята програма. Тук все още е бебе змия.

РЕДАКТИРАНЕ: Това е итеративната версия на findPrimeFactors, която написах. Знам, че не е оптимално - но бих искал поне да го направя достатъчно ефективен, за да отговаря на правилото за 1 минута на Project Euler. Всички предложения за подобрение или яснота се оценяват.

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

person A---    schedule 13.06.2011    source източник
comment
Освен: относно „len(primeFactors) е 1“, не искате да пишете това. е за идентичност на обект и Python не дава гаранции, че има само един целочислен обект, съответстващ на дадено число. Например опитайте „len(range(257)) е 257“. Вместо това просто напишете len(primeFactors) == 1.   -  person DSM    schedule 13.06.2011
comment
Между другото, вижте stackoverflow.com/questions/1651154/ за обяснение защо работи по този начин.   -  person DSM    schedule 13.06.2011
comment
Тази нишка прави мотивите за поведението на стойността по подразбиране много по-ясни. И промених кода си според първото ви предложение. Искате да кажете, че диапазон (257) е число с множество цели числа, съответстващи на него?   -  person A---    schedule 14.06.2011


Отговори (3)



Стойността по подразбиране на primeFactors се споделя между повикванията, така че когато я промените, тя остава променена за бъдещи повиквания.

Пример:

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

print foo()
print foo()

Изход:

[1]
[1, 1]

Трябва да върнете нов списък, вместо да променяте по подразбиране:

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

print foo()
print foo()

Изход:

[1]
[1]
person hammar    schedule 13.06.2011

Както бе споменато от hammar, стойността по подразбиране се създава само веднъж, когато функцията е дефинирана и се споделя между извиквания.

Обичайният начин за това е да се използва стойност на маркер като стойност по подразбиране:

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

Извън темата, но вашата функция findPrimeFactor() ще рекурсира веднъж за всеки намерен прост множител. Python не прави премахване на опашно извикване, така че вероятно трябва да пренапишете това, като използвате итерация вместо рекурсия.

person Remy Blank    schedule 13.06.2011
comment
Благодаря, пренаписах метода си с помощта на итерация и включих вашето предложение с помощта на стойността на маркера. Всъщност нямах избор, тъй като скриптът ми се срина, когато се опитах да намеря 10 001 прости числа. Моят метод обаче не отговаря на правилото за 1 минута на Euler Project - person A---; 14.06.2011
comment
Предполага се, че се провали, защото достигна лимита на дълбочината на рекурсия на python? Също така, бихте ли разяснили повече за задните повиквания? От Wikipedia: Повикванията в опашката са важни, защото могат да бъдат реализирани без добавяне на нова стекова рамка към стека на повикванията. По-голямата част от рамката на текущата процедура вече не е необходима и може да бъде заменена от рамката на крайното повикване, модифицирана по подходящ начин. След това програмата може да премине към извиканата подпрограма. Създаването на такъв код вместо стандартна последователност от повиквания се нарича елиминиране на опашката на повикване или оптимизиране на опашката на повикване. - person A---; 14.06.2011
comment
Това означава ли, че Python създава нова стекова рамка за всяко рекурсивно извикване в моя метод? - person A---; 14.06.2011
comment
Да, точно това означава и Python (по подразбиране) ви позволява да имате само 1000 стека. Можете да промените максималния размер на стека, но е по-добре да преработите алгоритъма си. - person kindall; 14.06.2011
comment
@kindall благодаря за пояснението @RemyBlank Включих итеративната версия на findPrimeFactors в моя код - опитвам се да го подобря - person A---; 14.06.2011