8

私はいつもこれに立ち向かいますが、どの方法で攻撃すればよいかわかりません。以下は、いくつかのシーズン ファクトを処理するための 2 つの方法です。

私が解決しようとしているのは、方法 1 または 2 のどちらを使用するか、およびそれぞれの長所と短所、特に大量の事実です。

methodone事実が利用可能であるため、わざわざそれらのリスト (特に大きなリスト) を作成するのは無駄に思えます。リストが十分に大きい場合、これにはメモリへの影響もあるはずです。また、Prolog の自然なバックトラッキング機能を利用していません。

methodtwo私のために再帰を行うためにバックトラックを利用しています。メモリ効率がはるかに高いと思いますが、一般的にこれを行うのは良いプログラミング方法ですか? 従うのは間違いなく醜いです、そして他の副作用があるかもしれませんか?

私が見ることができる1つの問題は、呼び出されるたびfailに、呼び出し元の述語に何かを返す能力を失うことです。だった場合methodtwo(SeasonResults)、意図的に述語を継続的に失敗させるためです。そのmethodtwoため、状態を保存するにはファクトをアサートする必要があります。

おそらく(?)メソッド2は、(大きな)リスト処理を行う必要がないため、高速になりますか?

リストがあれば、それが進むべき道だと想像できmethodoneます..またはそれは常に真実ですか?methodoneを使用してリストをファクトにアサートし、メソッド 2 を使用してそれらを処理することは、どのような状況でも理にかなっているでしょうか? 完全な狂気?

しかし、繰り返しになりますが、事実を主張することは非常に「費用のかかる」ビジネスであると読みました。そのため、大きなリストであっても、リストの処理が進むべき道でしょうか?

何かご意見は?または、(何の)状況に応じて、一方を使用して他方を使用しない方が良い場合がありますか?例えば。メモリの最適化には、事実のアサートを含む方法 2 を使用し、速度には方法 1 を使用しますか?

season(spring).
season(summer).
season(autumn).
season(winter).

 % Season handling
showseason(Season) :-
    atom_length(Season, LenSeason),
    write('Season Length is '), write(LenSeason), nl.

% -------------------------------------------------------------
% Method 1 - Findall facts/iterate through the list and process each
%--------------------------------------------------------------
% Iterate manually through a season list
lenseason([]).
lenseason([Season|MoreSeasons]) :-
    showseason(Season),
    lenseason(MoreSeasons).


% Findall to build a list then iterate until all done
methodone :-
    findall(Season, season(Season), AllSeasons),
    lenseason(AllSeasons),
    write('Done').

% -------------------------------------------------------------
% Method 2 - Use fail to force recursion
%--------------------------------------------------------------
methodtwo :-
    % Get one season and show it
    season(Season),
    showseason(Season),

    % Force prolog to backtrack to find another season
    fail.

% No more seasons, we have finished
methodtwo :-
    write('Done').
4

3 に答える 3

10

あなたの例を見てみましょう。とても単純なので、もっと複雑に想像してみましょう。しかし、あなたは副作用が不可欠であることを当然のことと考えているようです. 少し質問させてください:

あなたの例では、非常に興味深い発見をしました: すべての季節の名前は同じ長さです. なんと驚くべき洞察でしょう。しかし、待ってください、それは本当ですか?これを確認する最も簡単な方法は次のとおりです。

?-シーズン(S)、atom_length(S,L)。
S = ばね、
L = 6;
S = 夏、
L = 6;
S = 秋、
L = 6;
S = 冬、
L = 6。

findall/3の必要はありませんwrite/1

回答数が多い場合、目視検査は実用的ではありません。400シーズンを想像してみてください。しかし、これは次の方法で確認できます。

?-シーズン(S)、atom_length(S,L)、dif(L,6)。
間違い。

したがって、異なる長さの季節がないことは確かです。

それがあなたの質問に対する私の最初の答えです:

可能な限り、独自の副作用手順ではなく、トップレベルのシェルを使用してください! 副作用を完全に回避するために、物事をもう少し伸ばしてください。これは、障害によるループを最初から回避するための最良の方法です。

トップレベルのシェルに固執することが良い考えである理由は他にもあります:

  • プログラムがトップレベルで簡単にクエリできる場合、それらのテスト ケースを追加するのは簡単です。

  • トップレベル シェルは他の多くのユーザーによって使用されているため、十分にテストされています。あなた自身の文章には多くの場合、欠陥があり、テストされていません。制約を考えてください。フロートを書くことを考えてください。write/1フロートにも使用しますか?正確に読み戻せるようにフロートを記述する正しい方法は何ですか? でこれを行う方法あります。答えは次のとおりです。

ISO ではwriteq/1,2write_canonical/1,2write_term/2,3、オプションを使用quoted(true)すると、float を正確に読み取ることができることが保証されます。つまり、それらは同じです。(==)/2

  • トップレベル シェルは、有効な Prolog テキストを表示します。実際、答え自体がクエリです。トップレベルに貼り付けることができます-まったく同じ答えを取り戻すためだけです。このようにして、引用、エスケープ、ブラケットなど、Prolog のより風変わりではあるが避けられない詳細を学習します。Prolog パーサーは非常に寛容であることが多いため、それ以外の方法で構文を学習することは事実上不可能です。

  • あなたのプログラムは、おそらく宣言的推論によりアクセスしやすくなります。

おそらく、あなたの 2 つの手順methodonemethodtwoは間違っています: を書いた後に改行を忘れましたDone。そのmethodone, methodoneため、文字化けが含まれています。それを簡単にテストする方法は?

しかし、プログラムをもう少し詳しく見てみましょう。障害駆動型ループの典型的なことは、「ただ」副作用を行うものとして無邪気に開始されますが、遅かれ早かれより多くのセマンティック部分も引き寄せる傾向があるということです。あなたの場合、atom_length/2テストや推論に完全にアクセスできない障害駆動ループに隠されています。

効率に関する考慮事項

Prolog システムは、多くの場合、スタックの割り当てを解除することによって障害を実装します。したがって、失敗によるループではガベージ コレクタは必要ありません。これが、失敗によるループが効率的であると人々が信じている理由です。ただし、必ずしもそうであるとは限りません。findall(A, season(A), As)のすべての答えのような目標Aは、あるスペースにコピーされます。これはアトムのようなものにとっては些細な操作ですが、より大きな用語を想像してみてください。言う:

blam([])。
blam([L|L]) :- blam(L)。

bigterm(L) :- 長さ(L,64), blam(L).

多くのシステムでは、findall/3またはassertz/1この長期にわたってシステムがフリーズします。

また、SWI、YAP、SICStus などのシステムには、非常に洗練されたガベージ コレクターがあります。また、より高度な技術が必要となるため、故障によるループの使用を減らすことで、これらのシステムをさらに改善することができます。

于 2012-03-20T16:36:55.870 に答える
7

事実が利用可能であるため、方法1は無駄に思えます。なぜ、それらのリスト(特に大きなリスト)を作成するのが面倒なのですか。リストが十分に大きい場合、これもメモリに影響を与える必要がありますか?

はい、方法1はΘ(n)メモリを使用します。主な利点は、宣言型であるということです。つまり、論理的な意味が単純です。

方法2、Prologプログラマーがそれを呼ぶ「失敗駆動型ループ」は、一定のメモリを取り、手続き型であり、とにかく手続き型(超論理的)なことをしているときに好まれるかもしれません。つまり、I/Oコードではそれを使用しても問題ありません。

SWI-Prologには、このループを記述する3番目の方法があることに注意してください。

forall(season(S), showseason(S)).

showseasonこれは、の各バインディングで成功した場合にのみ機能しseason(S)ます。

于 2012-03-16T21:23:53.927 に答える
3

findallすでに使用している場合はmaplist、同様に使用しないでください:

findall(S, season(S), L), maplist( showseason, L).

どちらも純粋な論理 Prolog コアにはありません。はい、すべてのソリューションにリスト全体を割り当てます。

2番目の方法は「失敗駆動型ループ」と呼ばれ、失敗をバックトラックした後に以前のソリューションにたどり着く方法がないことを除いて、何も問題はありません。findallそれが超論理的である理由です。内部的には、アサートによって中間結果を格納する障害駆動型ループとして実装できます。したがって、2 番目の方法は、余分なメモリを割り当てないだけでなく、概念的にもクリーンです。これは通常、最上位の「ドライバー」(つまり、UI) 述語で使用されます。

于 2012-03-16T22:00:58.323 に答える