geniacllm/wiki40b huggingface dataset:
回文とは先頭から読んでも、末尾から反対に文字をたどって読んでも、同じ文になるある程度意味の通る文のことである。回文は実用性よりも、文字列操作の練習課題としてしばしば利用される。ここでの定義は回文を生成するのではなく、回文であるかどうかの検査である。ここでは「なかきよのとおのねふりのなふめさめふなのりふねのおとのよきかな」（長き夜の 遠の睡りの 皆目醒め 波乗り船の 音の良きかな）のように、文字の並びを対象とする。回文(_回文) :-        atom_chars(_回文,Chars),        リストの反転(Chars,Chars).一旦組込述語 atom_chars で文字のリストに変換する。ここまではいわば定石のようなものだが、回文の場合はここから、そのリストを反転し、引数が共通であることを示して、反転前と反転後が同じなのが回文であると宣言している。実例はもちろん?- 回文(なかきよのとおのねふりのなふめさめふなのりふねのおとのよきかな).true.?-である。回文にはsub_atom/5を使った定義もある。回文(_回文) :-        forall(sub_atom(_回文,N,1,_,_文字),sub_atom(_回文,_,1,N,_文字)).回文の文字列中の前方からN文字目一文字と後方からN文字目一文字の対はどれも同じ文字になる。そういうことをこの定義では述べている。これも細部を述べながら、かつ、宣言性を維持した美しい定義である。ただし、中間点まで確かめたならば回文と決定できるはずだが、ここでは全文を走査してしまっている。中間点で打ち切る定義は、sub_atom/5の第二、第四引数を比較する。回文(_回文) :-        forall((sub_atom(_回文,N,1,R,_文字),N =< R),sub_atom(_回文,_,1,N,_文字)).forall/2これで第一引数の走査が中間点に達すると第二引数の走査はしなくなる。これで中間点を過ぎるとN >= Rが偽となるから第二引数のsub_atom(_回文,_,1,N,_文字)の検査には進まなくなる。しかし、forall/2の第一引数のsub_atom/5の方は最後まで走査されてしまう。この問題も解決しようとすると、回文(_回文) :-        forall((sub_atom(_回文,N,1,R,_文字),N =< R; !,fail),sub_atom(_回文,_,1,N,_文字).これで中間点に達すると!,failが働いて、sub_atom/5へのバックトラックは止まる。しかし、; !,failの部分は、複雑で宣言性を損ねる記述になっている。forall/2の中の副目標の述語名がsub_atomに統一できなくなっている点でも、読み易さを損ねている。カットの説明を参照されたいが、ここでのN =< R; !,failを独立した副目標として定義し直すことは、カットの有効範囲の関係からできない。このような場合、日本語で解説して行くようなつもりで、冗長な述語表現を取ると上手く行く。回文(_回文) :-        回文とは中間点まで先頭からN文字目と末尾からN文字目の文字が同一の文字列である(_回文).回文とは中間点まで先頭からN文字目と末尾からN文字目の文字が同一の文字列である(_回文) :-        forall(中間点までのN文字目(_回文,N,_文字),末尾からN文字目(_回文,N,_文字)).中間点までのN文字目(_回文,N,_文字) :-        sub_atom(_回文,N,1,R,_文字),N =< R; !,fail.末尾からN文字目(_回文,N,_文字) :-        sub_atom(_回文,_,1,N,_文字).この方がずっと宣言的で明解な表現になっている。
above(X,Y) :-        on(X,Y).above(X,Y) :-        on(X,Z),        above(Z,Y).ここで on の定義は具体的に定義する。例えば、on(波平,サザエ).on(サザエ,カツオ).実行例?- above(波平,X).X = サザエ;X = カツオ;False?= above(波平,カツオ).True一つ以上離れた関係を above の第二節でので定義で、引数の変数を on を経ながら X - Z - Y と連鎖することによって表現している。経路が以下のような定義の時、ある駅から別の駅に到達できるかを示す 到達可能性 の定義は。経路(渋谷,神泉).経路(神泉,駒場東大前).経路(駒場東大前,池の上).経路(池の上,下北沢).到達可能性(_駅_1,_駅_2) :-        経路(_駅_1,_駅_2).到達可能性(_駅_1,_駅_2) :-        経路(_駅_1,_駅_3),        到達可能性(_駅_3,_駅_2).実行例?- 到達可能性(神泉,下北沢).true.検索は、(神泉,駒場東大前) → (駒場東大前,池の上) → (池の上,下北沢) と進み、第一節の本体で :- 経路(池の上,下北沢). が真となることにより、到達可能性は真となる。この定義を見比べると、経路/到達可能性とon/aboveさらに、既に例として見てきた親子/孫(あるいは先祖)の関係が同じアルゴリズムであることがわかる。
単位節要素の加算の例に示された"年齢"のような単位節（本体定義なのない節）を定義順に表示するには普通失敗駆動を利用する。年齢の定義次の通りだとする。年齢(山田,35).年齢(大島,20).年齢(清川,28).ここでは定義順に氏名,年齢を全て表示する方法を示す。年齢表示 :-      年齢(_氏名,_年齢),      writef('%t,%t\n',[_氏名,_年齢]),      fail.表示は組込述語 writef を使っている。無条件に偽になる組込述語があるため、バックトラックが起こり順に述語 年齢 の各節が呼び出されて表示される。これが失敗駆動だ。最終的には年齢/2の呼び出しは偽となるため、質問は False で終わる。実行例?- 年齢表示.山田,35大島,20清川,28false.?-一般に必ず偽となって終わる定義は、これを再利用する際に不都合が生じる場合が多い。真として終了するためには、年齢表示 :-      年齢(_氏名,_年齢),      writef('%t,%t\n',[_氏名,_年齢]),      fail.年齢表示.と書き加えるのみである。失敗駆動を用いることなく、再帰を使って上記の年齢を表示することは案外と難しい。年齢表示 :-      findall([_氏名,_年齢],年齢(_氏名,_年齢),L),      再帰による年齢表示(L).再帰による年齢表示([]).再帰による年齢表示([[_氏名,_年齢]|R]) :-      writef('%t\n',[_氏名,_年齢]),      再帰による年齢表示(R).完全に独立した存在である定義節から、情報の連関を付与されたものとしてのリストを得るために、findall　をここでも使用した。実はこの findall の中に失敗駆動が含まれている。つまり、ここでは findall を使うことによって失敗駆動を隠蔽していることになる。
repeat の定義は以下の通り単純である。repeat.repeat :- repeat.repeat 自体は究極の再帰プログラミングである。問題はこれをどのように使用するかで、?- repeat,read(X),write(X),nl,X=end_of_file.|: abc.abc|: 123.123|: end_of_file.end_of_fileX = end_of_filerepeat は再帰述語ではあるが、失敗駆動の起点としてのみ利用される。repeatに制御が返ってくるのは、後続する副目標が完全に失敗したときである。repeatまで制御は戻り、再び後続する副目標が実行される。まったく最初と同じ状態に戻って実行される。ということは、本来述語定義や定義された節の引数は実行中に書き換えないことが基本なので、後続の副目標を何度繰り返しても同じ結果となり、停止しないはずである。したがってrepeatを含む節がプログラマの期待通り停止するためには、その後続の副目標の実行に副作用があることが前提となる。この副作用による変化を読み取ってrepeatの節を停止するように工夫する。上記の場合、X = end_of_file.がそれである。次に、四季 という検索述語を repeat の前に置いてみる。四季(春).四季(夏).四季(秋).四季(冬).?- 四季(_季節),repeat,read(X),write(X),nl,X=end_of_file.|: abc.abc|: 123.123|: end_of_file.end_of_file_季節 = 春,X = end_of_file,季節 の選択が春から先に進んでいない。副目標 四季 には repeat があるためバックトラックしてこないことになる。
前節ですでに用いたが、Prolog には入力述語として古くから、1引数と2引数の read が存在した。この入力述語の魅力はアトム、数値、リストを含む複合項など、どんな項でも入力可能である点にある。入力された文字列は解析されて、引数と単一化される。?- read(X).|: 33.X = 33.?- read(X).|: a(b,c).X = a(b,c).?- read(X).33abc.ERROR: Stream user_input:?- read(a(b,c)).33.false.?- read(a(b,c)).a(b,c).true.?-のように使う。注意するべきことは、プロンプト |: の後の入力には正しく項が来なくてはならず、しかも、入力はピリオドで終了する必要がある。ごく一般的なカンマ区切りの「abc,def」の入力はシングルクォートで囲み「'abc,def'.」でなくてはならないことになる。これでは実務的には不自由なので、文字列が改行されて終了するまでを、アトムとして受け取る 行入力 を定義してみる。行入力(_行文字列) :-        get_char(C),        行入力_1(C,Chars),        atom_chars(_行文字列,Chars) .行入力_1('\n',[]) :- !.行入力_1(end_of_file,[]) :- !.行入力_1(C,[C|R]) :-        get_char(C2),        行入力_1(C2,R).これで改行によって、それまで入力された文字が一旦リストに収められ、それを atom_chars で変換して、アトムとして受け取ることができるようになった。ただし、入力はアトムだけに限られる。この点は read が項であったのとは異なる。行入力_1 の第二節に現れる end_of_file であるが、これは改行ではなくファイルの終端を意味する情報が入力されたことを意味する。この独特なアトムを Prolog では伝統的に用いている。実行例?- 行入力(X).abc,defX = 'abc,def'今度は |: のプロンプトが表示されない。このプロンプトの表示は項入力述語 read のいわばサービスであって、同じく組込述語であるが、get_char では表示されない。
整数入力 は repeat によく似ているが、第一節の末尾にカットがあるため、失敗駆動の起点としての繰り返しにはならない。整数入力検査に失敗した場合のみ、整数入力を繰り返す。整数入力(_整数) :-        行入力(_行入力),        整数入力検査(_行入力,_整数),!.整数入力(_整数) :-        整数入力(_整数).整数入力検査(_行入力,_整数) :-        read_term_from_atom(_行入力,_整数,[]),        integer(_整数),!.整数入力検査(_行入力,_) :-        writef('入力された文字列%tは整数ではありません。再入力をお願いします。\n',[_行入力]),        fail.read_term_from_atom は組込述語であり、アトム（文字列）を受け取りこれを解析して、Prolog の項に変換している。整数でない情報を受け取った時にその情報を表示させるためには、行入力 と同じ本体の中で、処理することが必要である。整数入力 の第二節で表示することは、この節が引数から get_char で得た情報を受け取っていない以上不可能である。入力エラーを表示させる際に、入力された文字列も共に表示したいのであるが、第一節の本体内の副目標でない限り、引数からこの情報を受け取ることはできない。実行例?- 整数入力(X).33X = 33?- 整数入力(X).abc入力されたabcは整数ではありません。再入力をお願いします。8X = 8?-整数入力 述語は repeat を使っても書くことができる。整数入力(_整数) :-        repeat,        行入力(_行入力),        整数入力検査(_行入力,_整数),!.repeat とほとんど同型の再帰述語の整数入力の代わりに repeat 自体を挿入することによって上記のように失敗駆動的な整数入力述語が定義できることは実に興味深い。整数入力(_整数) :-        行入力(_行入力),        整数入力検査(_行入力,_整数),!.整数入力(_整数) :-        整数入力(_整数).repeat.repeat :-        repeat.member(H,[H|T]).member(H,[_|T]) :-        member(H,T).append([],L,L).append([U|X],Y,[U|Z]) :-        append(X,Y,Z).整数入力の主述語と Prolog を代表する述語 repeat、member、append を比較した見た。極めて類似したパターンの述語であることが分かる。
Prolog ではキュー操作を差分リスト(重リストともいう)を使って表現している。差分リストとは二つのペアとなったリストを設定し、第二リストの全てが、第一リストの末尾に来るようにしたものである。第一リストのなかで第二リストと重ならない部分が、示したい情報・生きた情報である。例を示す。第一リスト 第二リスト 示したい情報[1,2,3,4]  [3,4]      [1,2][x,y,z]    [y,z]      [x][1,2,3|X]  [3|X]      [1,2][1,2,3|X]  [2,3|X]    [1][1,2,3|X]  X          [1,2,3]キュー操作では、もっぱらこの最後の表現を使う（[1,2,3|X] X → [1,2,3]）。ここでは、直感的理解を深めるためオペレータ「-」を使って（すなわち複合項として）キューを表現してみる。makeEmptyQueue(X-X).emptyQueue(X-Y) :- X == Y.dequeue(_要素,[_要素|X]-Y,X-Y).enqueue(_要素,X-[_要素|Y],X-Y).[1,2,3|X] のような構造を不完全データ構造と呼ぶ。[1,2,3,4] のように完結していないという意味で。しかし、すでに多くのプログラム例を見てきたが、append に代表されるように、Prolog の引数部分はこの不完全データ構造で満ちている。構造の不完全な部分、すなわち変数部分に新たに情報構造を単一化することによって、そしてその末尾に再び不完全データ構造が来るようにすることで構造を成長させる。不完全データ構造の多用は Prolog プログラムの最も顕著な特徴である。キューの操作は、以下のようにキューを以前、以後と対にして（ここでは A,B  B,C）持ち回ることで利用する。?- makeEmptyQueue(A), ・・・ ,enqueue(2,A,B), ・・・ ,dequeue(E,B,C), ・・・上記の例では、現在のキューは、最後の C と単一化された構造であるといえる。キュー C は現在は空である。E には 2 が取り出せた。ところで、上記のキュー定義では、オペレータ「-」を利用して直感的に差分を強調することによって宣言性を高めた。しかし、Prolog では、引数には極力、リストを排することはできないにしても、構造体を持たず、アトムのみで構成するほうがプログラムの見通しがよくなる。ここで、キュー操作を日本語に書き直しながら、定義を書き直す。空のキューを生成する(X,X).キューは空である(X,Y) :- X == Y.キューから要素を取り出す(_要素,[_要素|X],Y,X,Y).キューに要素を追加する(_要素,X,[_要素|Y],X,Y).X と Y がペアであることが不明瞭になってしまったが、これで自然な定義である。
キュー操作が出てきたところでスタック操作について述べる。スタックの定義はさらに簡単である。スタックを生成する([]).スタックに要素を追加する(_pushされる要素,_push前のスタック,[_pushされる要素|_push前のスタック]).スタックから要素を取り出す([_popされる要素|_pop後のスタック],_popされる要素,_pop後のスタック).利用法は、?- スタックを生成する(A), ・・ ,スタックに要素を追加する(2,A,B), ・・ ,スタックに要素を追加する(3,B,C), ・・ ,スタックから要素を取り出す(C,E,D), ・・・上記の例では現在のスタックは、D であり、内容は [3] である。2 は一度プッシュされたがその後ポップされてしまった。
リストによって与えられた集合要素のN個の組合せ。非決定性の述語である。組合せ(X,1,[A]) :-        member(A,X).組合せ([A|Y],N,[A|X]) :-        N > 1,        N_1 is N - 1,        組合せ(Y,N_1,X).組合せ([_|Y],N,A) :-        N > 1,        組合せ(Y,N,A).第二引数が1になる則ち組合せの最後の一個を残りリストXの中からmember/2を使って順に選択する。これが第一節の意味である。第二節と第三節はバックトラックして全てのN-1個の組合せを作る非決定性の再帰的述語のパターンである。ここまでで、N-1個の全組み合わせが組み上がることを前提に、第一節でまだ選択されていない最後の一個をこれまたmember/2によって全ての可能性を選択して付加している。実行例?- 組合せ([a,b,c,d],3,X).X = [a,b,c];X = [a,b,d];X = [a,c,d];X = [b,c,d];false全ての組合せを蒐集するには、findall を用いればよい。述語を非決定性に定義することを基本として、必要な場合に findall によって全解をリストに取得する。これは Prolog の代表的なプログラムスタイルである。全ての組合せ(L,N,LL) :-        findall(X,組合せ(L,N,X),LL).実行例?- 全ての組合せ([a,b,c,d],3,LL).LL = [[a,b,c],[a,b,d],[a,c,d],[b,c,d]]
異なるn個のものから異なるr個のものを選ぶ、組合せの総数は公式から、nCr(N,R,X) :-        U is N - R + 1,        階乗(U,N,K1),        階乗(R,K2),        X is K1 // K2 .と Prolog では定義される。階乗の定義はこのプログラム例の中で既出である。findall を使い、組合せ が解を生成するごとに1をリストに格納することによっても、組合せの総数を求めることができる。nCr(N,R,X) :-        findall(1,組合せ(N,R,_),L),        length(L,X).ここでは、格納する値を1としたが、実はこれはアトム、数値、変数、何が来てもよい。実行例?- nCr(4,3,X).X = 4
組合せ同様これも非決定性の述語として作る。組込述語のselect/3を使うことによって、更に洗練された述語に仕上がる。順列(_,0,[]).順列(L,N,[A|L2]) :-        select(A,L,R),        N_1 is N - 1,        順列(R,N_1,L2).select/3はmember/2に近い述語だが、ひとつ要素を取った残りが第三引数に単一化されている。そういう非決定性の述語である。?- 順列([a,b,c],2,L).L = [a, b] ;L = [a, c] ;L = [b, a] ;L = [b, c] ;L = [c, a] ;L = [c, b] ;false.のように順列が取れる。これをリストに取りたい場合は、ここでもfindall/3を使って、?- findall(L,順列([a,b,c],2,L),LL).LL = [[a,b],[a,c],[b,a],[b,c],[c,a],[c,b]].となる。
上記、組込述語select/3を定義してみる。select(_取り出す要素,_リスト,_残り要素のリスト) :-        append(_前リスト,[_取り出す要素|_後リスト],_リスト),        append(_前リスト,_後リスト,_残り要素のリスト).append/3を劇的に使うことによって、極めて宣言的に定義が出来上がる。select/3には他に、以下のような再帰的な定義がある。select(E,[E|R],R).select(E,[A|R2],[A|R3]) :-        select(E,R2,R3).これもmember/2やappend/3に似て、洗練された定義だが、直感的に理解させる力は上記のものに遠く及ばない。Prologの述語定義は再帰的なものが、宣言的であり、直観に訴えるものだとは必ずしも言えない事例である。
項を複写する。ただ、単なる複写ではない、大事な点がある。変数はそのまま複写することはしない。新たに別の変数を作り出して、それを複写元の変数があった構造上の同じ位置に置く。例から示そう。?- 項複写(a(あい,X,z(Y)),U).U = a(あい, _G1017, z(_G1020)).注意するべきは変数であり、ここではX -> _61017, Y -> _61020と明白に別の変数に置き換わって複写されている。それ以外の関数名 "a","z" アトム"あい"は変化していない。この述語は高階述語を使用し、かつそれが再帰の中で引数として引き継がれて行く場合に使用される。引数として受け取った高階の述語呼び出し項と基本的に同じ構造だが、変数だけは単一化されることのない項を作り出し、それを引数として渡す時の基礎とする。引数を受け取った時点と引き渡す時点の間で変数が単一化された場合、意図した同型の引き渡し述語とならず、再帰が失敗してしまうことを避けるために使用されることが多い。項複写のプログラムを示す前に、この高階述語の使用例を示してしまう。第一引数 P に述語が渡されることとする。foo(P,X) :-        項複写(P,Q),        call(P),        ・・・        foo(Q,X).P が単一化されてしまうと、次の再帰呼び出しでは、受け取った P とは単一化された、つまり、異なったデータを渡してしまうこととなる。Pの引数に変数があった場合そのことが起こる。引き続き変数で渡したいPであるが、これを単一化され具体化された値で渡してしまうことになる。往々にしてこの場合呼び出した再帰の副目標はプログラマの意図に反して偽となる。これを避けるために、この述語の入り口で P を複写して Q を作ってしまってそれを渡すようにする。もう少し具体的な例を示す。リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する([],_,[]).リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する([A|R1],P,[B|R3]) :-    arg(1,P,A),    call(P),    arg(2,P,B),    リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する(R1,P,R3).?- リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する([2,3,5],succ(X,Y),L).false.と失敗する。この原因はリストの第一要素2ではsucc(2,3),が得られるが、その状態で次の再帰の引数として、P乃ちsucc(2,3)が使われてしまうからだ。この問題を回避するのが項複写/2である。リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する([],_,[]).リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する([A|R1],P,[B|R3]) :-    項複写(P,P1),    arg(1,P,A),    call(P),    arg(2,P,B),    リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する(R1,P1,R3).?- リスト要素をPの第一引数と単一化してPを適用してPの第二引数を収集する([2,3,5],succ(X,Y),L).L = [3,4,6].項複写はPが実行される前に行わなければならないことが分るだろう。さて、本題に戻って項複写の定義は項複写(P,P1) :-        compound(P),        同一構造を生成(P,P1,A),        項引数複写(1,A,P,P1),!.項複写(P,P1) :-        var(P),!.項複写(P,P).同一構造を生成(P,P1,A) :-        functor(P,F,A),        functor(P1,F,A).項引数複写(M,N,P,P1) :-        M > N,!.項引数複写(M,N,P,P1) :-        '1引数を再帰的に項複写'(M,P,P1),        M1 is M + 1,        項引数複写(M1,N,P,P1).'1引数を再帰的に項複写'(M,P,P1) :-        arg(M,P,T),        arg(M,P1,T1),        項複写(T,T1).第一引数に複写前の項、第二引数に複写後の項がくる。項複写/2の第二節 P,P1 と変数が異なっている点が重要である。functor、arg は組込述語。functor は項を関数とアリティに分解する。arg は項の引数に値を与える。compound も組込述語で引数が複合項であるかどうか検査する。述語 1引数を再帰的に項複写 は第一文字が数字であるためシングルクォートで囲われている。実行例?- 項複写(a(b,U,c(V,W),d),X).U = _1,V = _2,W = _3,X = a(b,_4,c(_5,_6),d)項複写された変数は元の変数とは別のものなので、逆にこの変数を単一化する必要がある場合は、どこに変数があるか、あるいは何に単一化すればよいかわからないという事態が生じる。その可能性に対処するためには項複写を少し拡張する。ここで、項複写 の引数の数を増やし、第三引数に複写元の項の変数、第四引数に複写先の項の変数をそれぞれ順序正しくリストに取れるようにする。項複写(P,P1,VL1,VL2) :-        compound(P),        同一構造を生成(P,P1,A),        項複写(1,A,P,P1,VL1,VL2),!.項複写(P,P1,[P],[P1]) :-        var(P),!.項複写(P,P,[],[]).同一構造を生成(P,P1,A) :-        functor(P,F,A),        functor(P1,F,A).項引数複写(M,N,P,P1,[],[]) :-        M > N,!.項引数複写(M,N,P,P1,VL1,VL2) :-        ひとつの引数を再帰的に項複写(M,P,P1,V3,V4),        M1 is M + 1,        項引数複写(M1,N,P,P1,VL5,VL6),        append(VL3,VL5,VL1),        append(VL4,VL6,VL2),!.ひとつの引数を再帰的に項複写(M,P,P1,V3,V4) :-        arg(M,P,T),        arg(M,P1,T1),        項複写(T,T1,V3,V4).これで以下の実行例に見られるように、引数の対応を取ることができる。実行例?- 項複写(a(b,U,c(V,W),d),X,L1,L2),V = 8.U = _1,V = 8,W = _3,X = a(b,_4,c(8,_6),d),L1 = [U,8,W],L2 = [_4,_5,_6]この後、L1の中の変数とL2の中の変数はnth1/3の第一引数を共通にして取得する。    ・・・    nth1(_nth1,L1,A),    nth1(_nth1,L2,B),    ・・・別に変数が取られてしまったことが却って都合の悪い場合は、 A = B のように単一化して対応付ければよい。
プログラム例全体を通して、可能な限り述語名を日本語にしてきた。ここでもソートには整列という語があるが、一般にソートが使われる機会が圧倒的に多い。それでここは挿入ソートとした。与えられたリストを昇順にソートする挿入ソートのプログラムを示す。挿入ソート(L1,L2) :-        挿入ソート(L1,[],L2).挿入ソート([],L,L).挿入ソート([A|R],L1,L) :-        挿入(A,L1,L2),        挿入ソート(R,L2,L).挿入(A,[],[A]).挿入(A,[B|R],[A,B|R]) :-        A @=< B,!.挿入(A,[B|R1],[B|R2]) :-        A @> B,        挿入(A,R1,R2).空のリストから始めて一要素ずつ取り出し、整列した状態で成長するリストの適切な位置に挿入するという述語である。降順の挿入ソートの場合は 挿入 を挿入(A,[],[A]).挿入(A,[B|R],[A,B|R]) :-        A @>= B,!.挿入(A,[B|R1],[B|R2]) :-        A @< B,        挿入(A,R1,R2).のように比較演算子を反転させればよい。ただ、これでは 挿入 が二通りできてしまうため、昇順挿入(A,[],[A]).昇順挿入(A,[B|R],[A,B|R]) :-        A @=< B,!.昇順挿入(A,[B|R1],[B|R2]) :-        A @> B,        昇順挿入(A,R1,R2).降順挿入(A,[],[A]).降順挿入(A,[B|R],[A,B|R]) :-        A @>= B,!.降順挿入(A,[B|R1],[B|R2]) :-        A @< B,        降順挿入(A,R1,R2).のように明確に述語名を分けて定義するべきである。当然これら述語を副目標として呼び出す定義の述語名も 昇順挿入ソート、降順挿入ソート でなくてはならない。
与えられたリストを昇順にソートするクイックソートのプログラム例を示す。quicksort([],[]).quicksort([X|Xs], Ys) :-        partition(Xs, X, Smaller, Bigger),        quicksort(Smaller, L1),        quicksort(Bigger, L2),        append(L1, [X|L2], Ys).partition([], _, [], []).partition([X|Xs], Pivot, [X|Smalls], Bigs) :-        X @< Pivot,        partition(Xs, Pivot, Smalls, Bigs).partition([X|Xs], Pivot, Smalls, [X|Bigs]) :-        X @>= Pivot,        partition(Xs, Pivot, Smalls, Bigs).Pivotとは軸要素のことである。軸要素より小さい要素を整列、軸要素より大きい要素も整列。それを軸要素を挟んで結合したものが整列したリストだ。末尾再帰版（partitionは上に同じ）quicksort(Xs, Ys) :-        quicksort(Xs, Ys, []).quicksort([], Ys, Ys).quicksort([X|Xs], Ys, Ys_1) :-        partition(Xs, X, Smaller, Bigger),        quicksort(Smaller, Ys, [X|Ys_2]),        quicksort(Bigger, Ys_2, Ys_1).Ys-[], Ys-Ys_1, Ys-[X|Ys_2], Ys_2-Ys_1 に差分リストの関係が見られる。美しい定義であり、上記appendを使うクイックソートに較べて若干速いが、差分リストを分かっていない人に対して、言葉で説明して理解させる事は難しい。quicksort/3の第二・第三引数の位置が前の版のものと変わっている。一般にPrologでは最終引数を出力とすることが多いが、ここでは差分リストの理解を扶けるために逆転した。限定節文法によって記述されたもの（partitionは上に同じ）quicksort(Xs,Ys) :-        quicksort(Xs,Ys,[]).quicksort([])     --> [].quicksort([X|Xs]) -->       { partition(Xs, X, Smaller, Bigger) },        quicksort(Smaller), [X], quicksort(Bigger).実行例。?- quicksort([3,2,1,6],L).L = [1,2,3,6]?= quicksort([a,b,1,2],L).L = [1,2,a,b]?- quicksort([聖,徳,太子],L).L = [太子,徳,聖]?- quicksort([2,3,3,1,1,4],L).L = [1,1,2,3,3,4]実行例の最後に示すように、ここでの定義では Xs を集合と見なしていない。そのため重複する2番目以降の要素を削除することはしていない。集合解 L = [1,2,3,4] を期待する場合は、partition の定義をpartition([], _, [], []).partition([X|Xs], Pivot, [X|Smalls], Bigs) :-        X @< Pivot,        partition(Xs, Pivot, Smalls, Bigs).partition([X|Xs], Pivot, Smalls, [X|Bigs]) :-        X @> Pivot,        partition(Xs, Pivot, Smalls, Bigs).partititon([_|Xs], Pivot, Smalls, Bigs) :-        X = Pivot,        partition(Xs, Pivot, Smalls, Bigs).と文字変更する。すなわち、6行目の「X @>= Pivot」を「X @> Pivot」に変更する。なぜならば、すでに同一の値は Pivot 自身として存在するからである。さらにそれだけだと同一要素が出現すると partition は失敗してしまうから、同一要素は無視することの宣言である第四節を加える。組込述語 sort は、この集合解が返ってくる仕様に決められている。最後に昇順または降順を区別する問題は挿入ソートの部分で述べた方針で、このクイックソートも書き直す必要がある。
木構造整列(ヒープソート)はメモリ上のスタックやヒープ領域に木構造を構築することによってソートを実現する。ここでは、標準入力ファイルから値の列が与えられたものを、引数上(スタック上)に木構造を成長させて、読み込みを終了したら、昇順または降順に整列された値を取り出すことのできる述語木構造整列/1を定義してみる。木構造整列(_木構造) :-        read(_値),        木構造整列(_値,[],_木構造).木構造整列(end_of_file,_木構造,_木構造) :- !.木構造整列(_値_1,_木構造_1,_木構造) :-        挿入(_値_1,_木構造_1,_木構造_2),        read(_値_2),        木構造整列(_値_2,_木構造_2,_木構造).挿入(_値,[],_木構造) :-        木構造([],_値,[],_木構造).挿入(_値,_木構造_1,_木構造_2) :-        木構造(_左部分木_1,_値_1,_右部分木_1,_木構造_1),        挿入(_値,_左部分木_1,_値_1,_右部分木_1,_左部分木_2,_右部分木_2),        木構造(_左部分木_2,_値_1,_右部分木_2,_木構造_2).挿入(_値,_左部分木_1,_値_1,_右部分木,_左部分木_2,_右部分木) :-        _値 @=< _値_1,        挿入(_値,_左部分木_1,_左部分木_2).挿入(_値,_左部分木,_値_1,_右部分木_1,_左部分木,_右部分木_2) :-        _値 @> _値_1,        挿入(_値,_右部分木_1,_右部分木_2).木構造(_左部分木,_値,_右部分木,_木構造) :-        functor(_木構造,木,3),        arg(1,_木構造,_左部分木),        arg(2,_木構造,_値),        arg(3,_木構造,_右部分木).この木構造整列は標準入力からの整列に適したものであり、それ故に最初にそれを示したが、リストからの整列も可能である。木構造整列(_リスト,_木構造) :-        木構造整列(_リスト,[],_木構造).木構造整列([],_木構造,_木構造) :- !.木構造整列([_値_1|R],_木構造_1,_木構造) :-        挿入(_値_1,_木構造_1,_木構造_2),        木構造整列(R,_木構造_2,_木構造).と書き換えるだけである。次に、構築が終った木構造から順に昇順に値を取り出す非決定性の述語木構造整列からの昇順取り出し/2と木構造整列からの降順取り出し/2を定義し、それを使って昇順または降順に整列したリストを取り出す。木構造整列からの昇順取り出し(_木構造,_値) :-        木構造(_左部分木,_,_右部分木,_木構造),        整列木構造からの昇順取り出し(_左部分木,_値).木構造整列からの昇順取り出し(_木構造,_値) :-        木構造(_,_値,_,_木構造).木構造整列からの昇順取り出し(_木構造,_値) :-        木構造(_左部分木,_,_右部分木,_木構造),        木構造整列からの昇順取り出し(_右部分木,_値).木構造整列からの降順取り出し(_木構造,_値) :-        木構造(_左部分木,_,_右部分木,_木構造),        整列木構造からの降順取り出し(_右部分木,_値).木構造整列からの降順取り出し(_木構造,_値) :-        木構造(_,_値,_,_木構造).木構造整列からの降順取り出し(_木構造,_値) :-        木構造(_左部分木,_,_右部分木,_木構造),        木構造整列からの降順取り出し(_左部分木,_値).木構造整列からの昇順リスト(_木構造,_昇順に整列したリスト) :-        findall(_値,木構造整列からの昇順取り出し(_木構造,_値),_昇順に整列したリスト).木構造整列からの降順リスト(_木構造,_降順に整列したリスト) :-        findall(_値,木構造整列からの降順取り出し(_木構造,_値),_降順に整列したリスト).一般にPrologで引数に構造のあるデータが来ることは歓迎されない。関係データベースに比肩する平明さがこの言語の特長となっているからだ。この木構造整列に見られるグラフは例外的なものである。
ハノイの塔は最も Prolog 向きの課題のひとつとして知られる。ここでは、N枚の円盤を三本の柱のうち、一番左の柱から右の柱に移すケースを示す。円盤は下から上に向かうほど小さくなるように積まれ、常にその状態が維持されなくてはならない。:- op(500,xfx,から).:- op(400,xf,へ).ハノイの塔(N,_移動履歴) :-        length(Ln,N),        ハノイの塔(Ln,左柱,中柱,右柱,_移動履歴).ハノイの塔([_],_左,_中,_右,[_左 から _右 へ]).ハノイの塔([_|Ln],_左,_中,_右,_移動履歴) :-        ハノイの塔(Ln,_左,_右,_中,_移動履歴_1),        ハノイの塔(Ln,_中,_左,_右,_移動履歴_2),        append(_移動履歴_1,[_左 から _右 へ|_移動履歴_2],_移動履歴).冒頭、から を中置演算子、へ を後置演算子として定義している。それを使って _移動前の位置 から _移動後の位置 という項を表現して、それを履歴として残している。このプログラムは Prolog が宣言的であることを顕著に示す好例である。この述語で述べられていることは、三回が一組となって同じパターンが繰り返されるということ。さらに最も下の一枚が順に、この課題では右柱に積まれるのだが、新しい最下層の円盤が右柱の最上位に積まれた時には、それより上の塔は中柱、左柱と交互に完全に積み上がっている。それを、ハノイの塔述語の本体の二番目の副目標で、どちらの柱を起点として以後の移動を開始するかを、引数の位置を入れ替えることだけによって、表現している。このように全体の骨格と僅かな引数の置き換えだけによって、手続的動作の全てを暗示している。このような表現力に対して「宣言的」と言うのである。実行例?- ハノイの塔(4,L),   member(A,L),   writef('%t\n',[A]),   fail;   true.左柱 から 中柱 へ左柱 から 右柱 へ中柱 から 右柱 へ左柱 から 中柱 へ右柱 から 左柱 へ右柱 から 中柱 へ左柱 から 中柱 へ左柱 から 右柱 へ中柱 から 右柱 へ中柱 から 左柱 へ右柱 から 左柱 へ中柱 から 右柱 へ左柱 から 中柱 へ左柱 から 右柱 へ中柱 から 右柱 へtrue.?-
nクイーン問題も Prolog 向き問題の代表のひとつである。チェス盤が8×8であることから、8クイーンとして課題になることが多いが、ここでは盤面の大きさを一般化したnクイーンである。チェスのクイーンは縦横斜め、盤面の自分の位置から盤面の端までが全て利き筋となる駒である。この駒を各行に適宜ひとつずつ配置して、全てのクイーンの利き筋に他のクイーンがいないように、全ての列にクイーン配置せよという問題である。一つの解は列番号のリストで得られる。nクイーンの Prolog 定義は随分と工夫され、多くの作品がある。ここでは最も一般的な戦略の解法を示す。駒の利き筋という概念を示すために、可能な限り説明的な Prolog をコードを試みている。途中に現れる Qs という論理変数に、一つずつ解候補のクイーン位置が成長していく。nクイーン(_nクイーン,_一解) :-        '1からnまでの数リストを得る(縦横の利き筋検査はこれを行ごとに一つずつ選択することで回避される)'(_nクイーン,_数リスト),        行ごとに順に駒を置き利き筋を調べて一解を得る(_数リスト,[],_一解).行ごとに順に駒を置き利き筋を調べて一解を得る([],_一解,_一解).行ごとに順に駒を置き利き筋を調べて一解を得る(_位置リスト,Qs,_一解) :-        '駒の位置と残り駒の位置リストを得る(この選択自体が縦横の利き筋検査になっている)'(_駒の位置,                                                                                               _位置リスト,_残り駒の位置リスト),        斜めの利き筋に駒はない(_駒の位置,1,Qs),        行ごとに順に駒を置き利き筋を調べて一解を得る(_残り駒の位置リスト,[_駒の位置|Qs],_一解).'駒の位置と残り駒の位置リストを得る(この選択自体が縦横の利き筋検査になっている)'(_駒の位置,                                                                          [_駒の位置|_残り駒の位置リスト],_残り駒の位置リスト).'駒の位置と残り駒の位置リストを得る(この選択自体が縦横の利き筋検査になっている)'(X,[H|T],[H|T1]) :-        '駒の位置と残り駒の位置リストを得る(この選択自体が縦横の利き筋検査になっている)'(X,T,T1).斜めの利き筋に駒はない(_,_,[]).斜めの利き筋に駒はない(Q,_隔たり,[Q1|Qs]) :-        Q + _隔たり =\= Q1,        Q - _隔たり =\= Q1,        _隔たり_2 is _隔たり + 1,        斜めの利き筋に駒はない(Q,_隔たり_2,Qs).'1からnまでの数リストを得る(縦横の利き筋検査はこれを行ごとに一つずつ選択することで回避される)'(_nクイーン,_数リスト) :-        findall(M,between(1,_nクイーン,M),_数リスト).これは非決定的な述語である。上記の定義では、表示幅の制限から引数の途中で改行している部分があるが、引数の区切りである「,」の前後であるならば、これは構わない。6クイーンの場合の実行例を示す。?- nクイーン(6,L).L = [5, 3, 1, 6, 4, 2] ;L = [4, 1, 5, 2, 6, 3] ;L = [3, 6, 2, 5, 1, 4] ;L = [2, 4, 6, 1, 3, 5] ;false.?-ここでは4つの解が得られたが、8クイーンだと92解になる。
言語 (ab)* を受理する決定性有限オートマトンと非決定性有限オートマトンのシミュレート。%                               a%                  -------------------------->%         状態_0                                 状態_1%                  <--------------------------%                               b%%          言語(ab)*を受理する決定性有限オートマトンの遷移%'言語(ab)*を受理する決定性有限オートマトン'(_記号ならび) :-        '言語(ab)*初期状態'(_状態),        '言語(ab)*を受理する決定性有限オートマトン'(_状態,_記号ならび).'言語(ab)*を受理する決定性有限オートマトン'(_状態,[_記号|R]) :-        '言語(ab)*'(_状態,_記号,_状態_1),        '言語(ab)*を受理する決定性有限オートマトン'(_状態_1,R).'言語(ab)*を受理する決定性有限オートマトン'(_状態,[]) :-        '言語(ab)*終了状態'(_状態).'言語(ab)*初期状態'(状態_0).'言語(ab)*終了状態'(状態_0).'言語(ab)*'(状態_0,a,状態_1).'言語(ab)*'(状態_1,b,状態_0).%%                               a%                  -------------------------->%       状態集合_0 {0}                         状態集合_1 {1}%                  <--------------------------%                               b%%          言語(ab)*を受理する非決定性有限オートマトンの遷移%'言語(ab)*を受理する非決定性有限オートマトン'(_記号ならび) :-        '言語(ab)*初期状態'(_状態),        '言語(ab)*を受理する非決定性有限オートマトン'(_状態,_記号ならび).'言語(ab)*を受理する非決定性有限オートマトン'(_状態,[_記号|R]) :-        '言語(ab)*'(_状態集合,_記号,_状態集合_1),        状態集合(_状態集合,_状態),        状態集合(_状態集合_1,_状態_1),        '言語(ab)*を受理する決定性有限オートマトン'(_状態_1,R).'言語(ab)*を受理する非決定性有限オートマトン'(_状態,[]) :-        '言語(ab)*終了状態'(_状態).'言語(ab)*初期状態'(0).'言語(ab)*終了状態'(0).'言語(ab)*'(状態集合_0,a,状態集合_1).'言語(ab)*'(状態集合_1,b,状態集合_0).状態集合(状態集合_0,0).状態集合(状態集合_1,1).記号ならびが [a,b,a,b,a,b] のように a,b が対になって最後まで並んでいる場合だけ真となる。正規表現を正しく理解してプログラムテスト等に挑むためには、このようなコードの理解も必要である。非決定性有限オートマトンを状態集合で記述したが、これを'言語(ab)*' に展開してしまうことも可能である。例えば 状態集合_1 が {1,2} である場合には、'言語(ab)*'(0,a,1).'言語(ab)*'(0,a,2).'言語(ab)*'(1,b,0).'言語(ab)*'(2,b,0).と展開できる。複数行になるだけで、決定性有限オートマトンと形式的なの違いはなくなってしまう。決定性有限オートマトンは、このような複数の状態を書かないという約束事によって成立している。上記、有限オートマトンと同等の Prolog 述語としての定義は、'(ab)*'([]).'(ab)*'([a,b|R]) :-        '(ab)*'(R).とこれも相当に短いコードとなる。
Prolog のチューリング完全性は、チューリングマシンをシミュレートすることで示すことができる。turing(Tape0, Tape) :-        perform(q0, [], Ls, Tape0, Rs),        reverse(Ls, Ls1),        append(Ls1, Rs, Tape).perform(qf, Ls, Ls, Rs, Rs) :- !.perform(Q0, Ls0, Ls, Rs0, Rs) :-        symbol(Rs0, Sym, RsRest),        once(rule(Q0, Sym, Q1, NewSym, Action)),        action(Action, Ls0, Ls1, [NewSym|RsRest], Rs1),        perform(Q1, Ls1, Ls, Rs1, Rs).symbol([], b, []).symbol([Sym|Rs], Sym, Rs).action(left, Ls0, Ls, Rs0, Rs) :-        left(Ls0, Ls, Rs0, Rs).action(stay, Ls, Ls, Rs, Rs).action(right, Ls0, [Sym|Ls0], [Sym|Rs], Rs).left([], [], Rs0, [b|Rs0]).left([L|Ls], Ls, Rs, [L|Rs]).以下のルールは簡単なチューリングマシンの例である。状態遷移と動作を Prolog の節として表現している。rule(q0, 1, q0, 1, right).rule(q0, b, qf, 1, stay).このチューリングマシンは単進符号化（1の並びで符号化）した数値に1を加える。つまり、任意個の1のセル上をループし、最後に1を追加する。?- turing([1,1,1], Ts).Ts = [1, 1, 1, 1] ;この例から、状態間の関係を Prolog で表現することで、任意の計算が状態遷移のシーケンスとして宣言的に表現できることが分かる。
以下のPrologプログラムは動的計画法を使って2つのリストの最長共通部分列問題を多項式時間で求める。Prolog節によるデータベースを部分計算の結果のメモ化に用いている。:- dynamic(stored/1).memo(Goal) :-       ( stored(Goal) -> true ; Goal, assertz(stored(Goal)) ).lcs([], _, []) :- !.lcs(_, [], []) :- !.lcs([X|Xs], [X|Ys], [X|Ls]) :-        !,        memo(lcs(Xs, Ys, Ls)).lcs([X|Xs], [Y|Ys], Ls) :-        memo(lcs([X|Xs], Ys, Ls1)),        memo(lcs(Xs, [Y|Ys], Ls2)),        length(Ls1, L1), length(Ls2, L2),        ( L1 >= L2 -> Ls = Ls1 ; Ls = Ls2 ).実行例:?- lcs([x,m,j,y,a,u,z], [m,z,j,a,w,x,u], Ls).Ls = [m, j, a, u]