非終了の理由
まず、あなたの定義が元に戻せない理由を理解してみましょう。それをよりよく説明するために、失敗スライスを使用します。クエリを考えてみてtree_len(X,1).
ください
?- tree_len(T,1).
T = n(_G267, leaf, leaf)
しかし、別の答えを求めてはいけません。ループしてしまうからです:
?- tree_len(T,1).
T = n(_G267, leaf, leaf) ;
** LOOPS **
そのため、私たちが得た答えは少し気を散らすものでした。一見、すべて問題ないように見えましたが、後戻りして初めて実際の問題が発生しました。これは、Prolog で慣れる必要があるものです。明らかに、3 を使用したクエリの方が適切に選択されています。しかし、それは運でした。
一般的にこれを確実にする簡単な方法があります。false
追加の目標をクエリに追加するだけです。追加false
するということは、もはや成功することができないため、答えに関心がなくなったことを意味します。このようにして、気を散らすものはすべて取り除かれ、問題に直接直面します。
?- tree_len(T,1), false.
** LOOPS **
では、このループはどこから来たのでしょうか。
純粋で単調な Prolog プログラム (このようなもの) では、プログラムにいくつかの目標を追加することで、非終了の理由をローカライズできfalse
ます。結果のプログラム ( failure-sliceと呼ばれる) が終了しない場合、元のプログラムも終了しません。これは、クエリの最小の失敗スライスです。
?- tree_len(T,1), false .
tree_len(n(_,leaf,leaf),1) :- false .
tree_len(n(op,G,D),N) :-
tree_len(G,TG), false ,
tree_len(D,TD) ,
N は TG+TD .
私たちのプログラムは残りわずかです!非終了の原因はこの小さな断片です。問題を解決したいのであれば、その小さな部分で何かをしなければなりません。他のすべては無駄です。
したがって、このフラグメントがループしないようにプログラムを変更する必要があります。
実際には、2 つの選択肢があります。後続の演算またはclpfd のような制約を使用できます。前者は任意の Prolog システムで利用でき、後者は SWI、YAP、SICStus、GNU、B などの一部でのみ提供されます。
ここで、3 は で表されs(s(s(0)))
ます。
tree_lensx(T, s(N)) :-
tree_lendiff(T, N,0)。
tree_lendiff(n(_,leaf,leaf), N,N)。
tree_lendiff(n(op,G,D), s(N0),N) :-
tree_lendiff(G, N0,N1),
tree_lendiff(D、N1、N)。
ここでは、いくつかの一般的なコーディング手法を使用しました。
違い
実際の関係はtree_lendiff/3
、1 つの引数ではなく、2 つの引数を使用して自然数を表すことです。実際の数は、両者の差です。このようにして、定義を可逆的に保持することが可能です。
左再帰の回避
もう 1 つの手法は、左再帰を回避することでした。tree_lendiff/3
実際に記述される長さは、長さから 1を引いたものです。最初に得た失敗スライスを覚えていますか? まったく同じ障害スライスがここにも存在します! ただし、長さを 1 つずつ「シフト」することで、再帰規則の先頭が確実に終了するようになりました。
もともと、有限領域に対する制約は、組み合わせ問題を解決するために開発されました。しかし、それらを使用して可逆演算を取得することもできます。SWI と YAP での実装は、従来の非可逆コードと同等の効率であり(is)/2
ながら、可逆コードにコンパイルされるほどまで進んでいます。
:- use_module(library(clpfd)).
tree_fdlen(n(_,leaf,leaf),1).
tree_fdlen(n(op,G,D),N) :-
N #= TG+TD,
TG #>= 1,
TD #>= 1,
tree_fdlen(G,TG),
tree_fdlen(D,TD).
このプログラムは、元の定義により厳密に対応しています。それにもかかわらず、このプログラムの終了を確実にするために冗長な情報を追加した 2 つの目標に注意してTG #>= 1
ください。TD #>= 1
次のように、特定の範囲のすべてのツリーを列挙できるようになりました。
?- Size in 0..4, tree_fdlen(T, Size).
Size = 1, T = n(_A,leaf,leaf) ;
Size = 2, T = n(op,n(_A,leaf,leaf),n(_B,leaf,leaf)) ;
Size = 3, T = n(op,n(_A,leaf,leaf),n(op,n(_B,leaf,leaf),n(_C,leaf,leaf))) ;
Size = 4, ... ;
Size = 4, ... ;
Size = 3, ... ;
Size = 4, ... ;
Size = 4, ... ;
Size = 4, ... ;
false.
解答置換の正確な順序に注意してください! 1、2、3、4 だけではありません。むしろ、答えはある順序で見つかります。すべてのソリューションを見つけることだけに関心がある限り、どれでもかまいません。