it-swarm.dev

Ошибка рекурсивной функции Python: "превышена максимальная глубина рекурсии"

Я решил проблему 10 Project Euler с помощью следующего кода, который работает методом грубой силы:

def isPrime(n):

    for x in range(2, int(n**0.5)+1):
        if n % x == 0:
            return False
    return True


def primeList(n):

    primes = []

    for i in range(2,n):
        if isPrime(i):
            primes.append(i)

    return primes


def sumPrimes(primelist):
    prime_sum = sum(primelist)
    return prime_sum


print (sumPrimes(primeList(2000000)))

Три функции работают следующим образом: 

  1. isPrime проверяет, является ли число простым числом;
  2. primeList возвращает список, содержащий набор простых чисел для определенного диапазона с пределом 'n', и; 
  3. sumPrimes суммирует значения всех чисел в списке. (Эта последняя функция не нужна, но мне понравилась ее ясность, особенно для такого начинающего, как я.)

Затем я написал новую функцию primeListRec , которая делает то же самое, что и primeList , чтобы помочь мне лучше понять рекурсию:

def primeListRec(i, n):
    primes = []
    #print i


    if (i != n):
        primes.extend(primeListRec(i+1,n))

    if (isPrime(i)):
        primes.append(i)
        return primes


    return primes

Вышеупомянутая рекурсивная функция работала, но только для очень маленьких значений, таких как «500». Функция вызвала сбой моей программы, когда я установил «1000». И когда я ввел значение типа «2000», Python дал мне это: 

RuntimeError: превышена максимальная глубина рекурсии .

Что я сделал не так с моей рекурсивной функцией? Или есть какой-то конкретный способ избежать предела рекурсии? 

18
anonnoir

Рекурсия - не самый идиоматический способ сделать что-то в Python, так как она не имеет хвостовой рекурсии оптимизации, что делает нецелесообразным использование рекурсии в качестве замены итерации (даже если в вашем примере функция не является хвостовой). рекурсивный, это не помогло бы в любом случае). По сути, это означает, что вы не должны использовать его для вещей, которые имеют сложность больше, чем линейную, если вы ожидаете, что ваши входные данные будут большими, (тем не менее, это нормально для вещей, которые имеют логарифмическую глубину рекурсии, таких как алгоритмы «разделяй и властвуй», как QuickSort ).

Если вы хотите попробовать этот подход, используйте язык, более подходящий для функционального программирования, такой как LISP, Scheme, Haskell, OCaml и т. Д .; или попробуйте Stackless Python, который имеет более широкие ограничения в использовании стека, а также имеет оптимизацию хвостовой рекурсии :-)

Кстати, хвостовой рекурсивный эквивалент вашей функции может быть:

def primeList(n, i=2, acc=None):
    return i > n and (acc or []) or primeList(n, i+1, (acc or []) + (isPrime(i) and [i] or []))

Другой «кстати», вы не должны создавать список, если вы используете его просто для сложения значений ... Pythonic способ решения 10-й проблемы Project Euler:

print sum(n for n in xrange(2, 2000001) if all(n % i for i in xrange(2, int(n**0.5)+1)))

(Хорошо, возможно, разделение его на несколько строк было бы еще более Pythonic, но я люблю один лайнер ^ _ ^)

26
fortran

Как уже говорилось, в языках, которые не могут работать с глубокими стеками, лучше использовать итеративный подход. В частности, в вашем случае лучше изменить используемый алгоритм. Я предлагаю использовать Сито Эратосфена , чтобы найти список простых чисел. Это будет довольно быстро, чем ваша текущая программа.

1
Thiago Chaves

Ну, я не эксперт по питону, но я предполагаю, что вы достигли stack limit. В этом проблема рекурсии, это замечательно, когда вам не нужно повторять много раз, но бесполезно, когда количество рекурсий становится даже умеренно большим.

Идеальная альтернатива - переписать ваш алгоритм, чтобы использовать вместо него итерацию.

Edit: На самом деле, присмотревшись к вашей конкретной ошибке, вы можете обойти ее, изменив sys.getrecursionlimit . Это займет у вас только так далеко, хотя. В конце концов вы получите исключение stackoverflow, которое возвращает меня к исходной точке.

1
Adrian

Вы перебираете n чисел и повторяетесь на каждом шаге. Поэтому предел рекурсии Python определяет ваш максимальный номер ввода. Это явно нежелательно. Тем более, что проблемы Эйлера обычно имеют дело с довольно большими числами.

0
Johannes Charra