web-dev-qa-db-ja.com

Python再帰関数エラー:「最大再帰深度を超えました」

プロジェクトオイラーの問題10を次のコードで解決しました。

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)))

3つの機能は次のように機能します。

  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:最大再帰深度を超えました

再帰関数で何が間違っていましたか?または、再帰制限を回避する特定の方法はありますか?

22
anonnoir

再帰は tail recursion 最適化を持たないため、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 []))

別の「ところで」、値を加算するためだけに使用している場合はリストを作成しないでください... Project Eulerの10番目の問題を解決するPython的な方法は次のとおりです。

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

(OK、多分それをさまざまな行に分割することはさらにPythonになりますが、ライナーが1つ大好きです^ _ ^)

30
fortran

既に述べたように、ディープスタックを処理できない言語では、反復的なアプローチを取る方が良いでしょう。特にあなたの場合、使用するアルゴリズムを変更するのが最善です。 エラトステネスのふるい を使用して素数のリストを見つけることをお勧めします。現在のプログラムよりもかなり高速です。

1
Thiago Chaves

さて、私はpythonエキスパートですが、 stack 制限に達したと思います。それは再帰の問題です。再帰する必要がないときは素晴らしいことです。何度も繰り返しますが、再帰の数が適度に大きくなると良くありません。

理想的な代替策は、代わりに反復を使用するようにアルゴリズムを書き換えることです。

編集:実際に特定のエラーを詳しく調べたので、 sys.getrecursionlimit を変更することでそれを乗り越えることができます。ただし、ここまではあなただけです。最終的には、元のポイントに戻るスタックオーバーフロー例外が発生します。

1
Adrian

N個の数字を反復処理し、各ステップで再帰しています。したがって、Pythonの再帰制限は最大入力数を定義します。それは明らかに望ましくありません。特に、オイラーの問題は通常、かなり大きな数値を扱うためです。

0
Johannes Charra