次の階乗プログラムを理解するのに問題があります
fact1(0,Result) :-
Result is 1.
fact1(N,Result) :-
N > 0,
N1 is N-1,
fact1(N1,Result1),
Result is Result1*N.
fact1
が2番目のfact1
内にネストされて呼び出された場合、それは最後の行Result is Result1*N.
が呼び出されないことを意味しませんか?または、Prologでは、再帰呼び出しの前に最後の行が実行されますか?
いいえ、再帰呼び出しが最初に発生します。そうでなければ、最後の節は無意味です。アルゴリズムは次のように分類されます。
factorial(0) => 1
factorial(n) => factorial(n-1) * n;
ご覧のとおり、正しい値を返すには、乗算する前に再帰の結果を計算する必要があります。
プロローグの実装には、おそらくトレースを有効にする方法があります。これにより、アルゴリズム全体が実行されていることを確認できます。それはあなたを助けるかもしれません。
ところで、基本的な再帰を理解したら、可能な限り末尾再帰を達成するようにしてください。ここでは次のようになります。
_factorial(N, R) :- factorial(N, 1, R).
factorial(0, R, R) :- !.
factorial(N, Acc, R) :-
NewN is N - 1,
NewAcc is Acc * N,
factorial(NewN, NewAcc, R).
_
末尾再帰では、以前に使用した再帰とは異なり、インタープリター/コンパイラーが再帰の次のステップに進むときにコンテキストをフラッシュできます。したがって、factorial(1000)
を計算すると、バージョンは1000コンテキストを維持しますが、私のバージョンは1のみを維持します。つまり、バージョンは最終的に目的の結果を計算せず、_Out of call stack memory
_エラーでクラッシュします。
あなたは 続きを読む ウィキペディアでそれについてすることができます。
それを行うためのかなり簡単な方法はこれです:
fac(0,1).
fac(N,F):-
N>0,N1 is N-1,fac(N1,F1),F is N*F1.
一般的に言えば、 @ m09の答え は基本的に末尾再帰の重要性について正しいです。
大きなN
の場合、積の計算方法が異なります。 「線形リスト」ではなく「二分木」を考えてください...
両方の方法を試して、ランタイムを比較してみましょう。まず、@ m09のfactorial/2
:
?-time((factorial(100000、_)、false))。 %200,004推論、1.606 CPU in 1.606 秒(100%CPU、124513リップ) false。
次に、ツリースタイルで実行します。 meta-predicatereduce/3
と lambda expression を使用します。
?-time((numlist(1,100000、Xs)、reduce(\ X ^ Y ^ XY ^(XY is X * Y)、Xs、_)、false))。 %1,300,042推論、0.264 CPU in 0.264 秒(100%CPU、4922402リップ) false。
最後に、専用の補助述語x_y_product/3
を定義して使用しましょう。
x_y_product(X, Y, XY) :- XY is X*Y.
何を得るのですか? ストップウォッチに聞いてみましょう!
?-time((numlist(1,100000、Xs)、reduce(x_y_product、Xs、_)、false))。 %500,050推論、0.094 CPU in 0.094 秒(100%CPU、5325635リップ) false。
ベースケースが宣言されています。 Nが正であり、前の項と乗算する必要があるという条件。
factorial(0, 1).
factorial(N, F) :-
N > 0,
Prev is N -1,
factorial(Prev, R),
F is R * N.
走る:
階乗(-1、X)。
factorial(1, 1).
factorial(N, Result) :- M is N - 1,
factorial(M, NextResult), Result is NextResult * N.
簡単な方法:
factorial(N, F):- N<2, F=1.
factorial(N, F) :-
M is N-1,
factorial(M,T),
F is N*T.