list_sum([], 0).
list_sum([Head | Tail], TotalSum) :-
list_sum(Tail, Sum1),
Total = Head + Sum1.
このコードはtrue
を返します。 Total = Head + Sum1
をTotal is Head + Sum1
に置き換えると、値が返されます。しかし、私がこのような結果を得るためにそれを置き換えるべきもの:
?- list_sum([1,2,0,3], Sum).
Sum = 1+2+0+3 ; % not to return value 6!!!
プロシージャの2番目の句では、TotalSumはインスタンス化されないことに注意してください。コードを調べるときに、インタープリターから警告を受け取っているはずです。
これが私の提案です:
list_sum([Item], Item).
list_sum([Item1,Item2 | Tail], Total) :-
list_sum([Item1+Item2|Tail], Total).
最初の節は、リストに要素が1つしか残っていない場合の基本ケースを扱います。これが結果です。
2番目の節は、再帰ステップを扱います。リストの最初の2つの項目を取得し、これらの2つの項目を新しい用語Item1 + Item2で置き換える再帰呼び出しを実行します。
答えは簡単です:
sum_list([], 0).
sum_list([H|T], Sum) :-
sum_list(T, Rest),
Sum is H + Rest.
このコードは一方向のみで機能します。つまり、特定の合計でリストを生成することはできません。しかし、そのようなリストのセットは無限であるため、それはいずれにしても実用的ではありません。
Prologでは、_(+)/2
_はバイナリinfix演算子です。これにより、+(A,B)
の代わりに_A+B
_を書き込むことができます。
?-current_op(_、yfx、+)。 %左連想二項中置演算子 true。
_(+)/2
_は左側に関連付けられるため、_1+2+3
_は_(1+2)+3
_の省略形です。
_(.)/2
_はrightに関連付けられるため、_[1,2,3]
_は.(1,.(2,.(3,[])))
の略です。
括弧を正しくするために、追加の「アキュムレータ」引数を持つ補助述語を使用します。
_list_sum([X|Xs],S) :-
list_sum0_sum(Xs,X,S).
list_sum0_sum([], S ,S).
list_sum0_sum([X|Xs],S0,S) :-
list_sum0_sum(Xs,S0+X,S).
_
サンプルクエリ:
_?- list_sum([1,2,0,3],S).
S = 1+2+0+3.
_
プログラムは
list_sum([],0).
list_sum([Head|Tail], TotalSum):-
list_sum(Tail, Sum1),
TotalSum is Head+Sum1.
クエリが
?- list_sum([1,2,3,4], Sum).
答えは
Sum = 10
数値のリストを加法式に変換する場合は、
[1,2,3]
に
1 + 2 + 3
差分リストのようなものを使用して、このようなことをすることができます:
list_to_additive_expr( [] , 0 ).
list_to_additive_expr( [X|Xs] , X + RHS ) :-
sum_of( Xs , RHS ).
または、アキュムレータを使用することもできます。
list_to_additive_expr( Xs , Expr ) :-
list_to_additive_expr( Xs , 0 , Expr )
.
list_to_additive_expr( [] , Expr , Expr ) .
list_to_additive_expr( [X|Xs] , RHS , Expr ) :-
sum_of( Xs , X + RHS , Expr )
.
最初のスタイルが正しくないことがわかると思いますtail recursiveしたがって、-tail recursionoptimization(TRO)によってループに最適化されません—そして、もし、リストは十分に長いため、スタックオーバーフローが発生します。 2番目のアプローチではTROを適用し、任意の長さのリストで機能する必要があります。
TROとは何でしょうか?これが ウィキペディアに答えがあります :
コンピュータサイエンスでは、末尾呼び出しは別のプロシージャ内で発生するサブルーチン呼び出しであり、戻り値を生成します。この戻り値は、呼び出し元のプロシージャによってすぐに返されます。その場合、呼び出しサイトは末尾の位置、つまり呼び出し手順の最後にあるといいます。サブルーチンがそれ自体に対して末尾呼び出しを実行する場合、それは末尾再帰と呼ばれます。これは再帰の特別なケースです。
呼び出し呼び出しに新しいスタックフレームを追加せずに実装できるため、呼び出し呼び出しは重要です。現在のプロシージャのフレームのほとんどは不要になりました。必要に応じて変更されたテールコールのフレームに置き換えることができます(プロセスのオーバーレイに似ていますが、関数呼び出しの場合)。その後、プログラムは呼び出されたサブルーチンにジャンプできます。標準の呼び出しシーケンスの代わりにそのようなコードを生成することは、末尾呼び出しの除去、または末尾呼び出しの最適化と呼ばれます。