web-dev-qa-db-ja.com

Prologのリストの要素の合計

list_sum([], 0).
list_sum([Head | Tail], TotalSum) :-
    list_sum(Tail, Sum1),
    Total = Head + Sum1.

このコードはtrueを返します。 Total = Head + Sum1Total is Head + Sum1に置き換えると、値が返されます。しかし、私がこのような結果を得るためにそれを置き換えるべきもの:

?- list_sum([1,2,0,3], Sum).
Sum = 1+2+0+3 ; % not to return value 6!!!
12
user721588

プロシージャの2番目の句では、TotalSumはインスタンス化されないことに注意してください。コードを調べるときに、インタープリターから警告を受け取っているはずです。

これが私の提案です:

list_sum([Item], Item).
list_sum([Item1,Item2 | Tail], Total) :-
    list_sum([Item1+Item2|Tail], Total).

最初の節は、リストに要素が1つしか残っていない場合の基本ケースを扱います。これが結果です。

2番目の節は、再帰ステップを扱います。リストの最初の2つの項目を取得し、これらの2つの項目を新しい用語Item1 + Item2で置き換える再帰呼び出しを実行します。

8
gusbro

答えは簡単です:

sum_list([], 0).
sum_list([H|T], Sum) :-
   sum_list(T, Rest),
   Sum is H + Rest.

このコードは一方向のみで機能します。つまり、特定の合計でリストを生成することはできません。しかし、そのようなリストのセットは無限であるため、それはいずれにしても実用的ではありません。

22
Rok Kralj

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.
_
2
repeat

プログラムは

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
Rohan

数値のリストを加法式に変換する場合は、

[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とは何でしょうか?これが ウィキペディアに答えがあります

コンピュータサイエンスでは、末尾呼び出しは別のプロシージャ内で発生するサブルーチン呼び出しであり、戻り値を生成します。この戻り値は、呼び出し元のプロシージャによってすぐに返されます。その場合、呼び出しサイトは末尾の位置、つまり呼び出し手順の最後にあるといいます。サブルーチンがそれ自体に対して末尾呼び出しを実行する場合、それは末尾再帰と呼ばれます。これは再帰の特別なケースです。

呼び出し呼び出しに新しいスタックフレームを追加せずに実装できるため、呼び出し呼び出しは重要です。現在のプロシージャのフレームのほとんどは不要になりました。必要に応じて変更されたテールコールのフレームに置き換えることができます(プロセスのオーバーレイに似ていますが、関数呼び出しの場合)。その後、プログラムは呼び出されたサブルーチンにジャンプできます。標準の呼び出しシーケンスの代わりにそのようなコードを生成することは、末尾呼び出しの除去、または末尾呼び出しの最適化と呼ばれます。

1
Nicholas Carey