Объем списков в 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, который дает мне некоторые подсказки, но рекурсия еще больше усложняет .

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

РЕДАКТИРОВАТЬ: это итеративная версия findPrimeFactors, которую я написал. Я знаю, что это не оптимально, но я хотел бы, по крайней мере, сделать его достаточно эффективным, чтобы соответствовать правилу 1 минуты проекта Эйлера. Любые предложения по улучшению или ясности приветствуются.

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) is 1», вы не хотите этого писать. is предназначен для идентификации объекта, и Python не дает никаких гарантий, что данному числу соответствует только один целочисленный объект. Например, попробуйте «len (диапазон (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
Спасибо, я переписал свой метод, используя итерацию, и включил ваше предложение, используя значение маркера. На самом деле у меня не было выбора, так как мой скрипт разбился, когда я попытался найти 10001 простое число. Однако мой метод не соответствует правилу 1 минуты проекта Эйлера. - person A---; 14.06.2011
comment
Предположительно это не удалось, потому что он достиг предела глубины рекурсии python? Кроме того, не могли бы вы подробнее рассказать о хвостовых вызовах? Из Википедии: Хвостовые вызовы важны, потому что они могут быть реализованы без добавления нового кадра стека в стек вызовов. Большая часть кадра текущей процедуры больше не нужна, и она может быть заменена кадром хвостового вызова, измененным соответствующим образом. Затем программа может перейти к вызываемой подпрограмме. Создание такого кода вместо стандартной последовательности вызовов называется устранением хвостового вызова или оптимизацией хвостового вызова. - 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