geniacllm/wiki40b huggingface dataset:
行列も集合同様、Prologでは特別な記法は用意されてはいない。そのため一般に行列を、リストを要素として持つリストとして表現することが多い。例えば 3 × 3 の単位行列は [[1,0,0],[0,1,0],[0,0,1]] のように表す。全体が3要素のリスト、そのそれぞれの要素がまた3要素のリストである。ここでは、findall/3を二重に使った行列の転置の定義を示す。行列の転置([_最初の行|_残りの行],_転置行列) :-        length(_最初の行,_列数),        findall(_転置された行,(                    between(1,_列数,_nth1),                    findall(_値,(                                member(_行,[_最初の行|_残りの行]),                                nth1(_nth1,_行,_値)),                            _転置された行)),                _転置行列).この定義の難しさは、列数を得るための表現にある。ここでは行列の転置述語の第一引数を細工してこれを得たが、代償として、対象行列を[_最初の行|_残りの行]と表現したため、この引数が何を意味するのかわかりにくいコードとなった。行列が[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]として与えられた時の行列の転置は実行例?- 行列の転置([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],_転置行列)._転置行列 = [[1,4,7,10],[2,5,8,11],[3,6,9,12]]となる。行列の転置の再帰的な定義は行列の転置(L,[L1|R2]) :-        行列の転置(L,L2,L1),        行列の転置(L2,R2).行列の転置([],[],[]) :- !.行列の転置([[A|R1]|R2],[R1|R3],[A|R4]) :-        行列の転置(R2,R3,R4).findall/3の定義に比べると、定義自体は簡素なのだが、可読性はかなり悪い。
行列の掛算は普通第二引数の行列を一旦、行列の転置/2 で転置し、掛け合わせる3つの述語 行列の掛算_1/3 行列の掛算_2/3 行列の掛算_3/3 によって積を得る。述語名の末尾に _1 _2 _3 を付加して別の述語とするのは、引数が同じで同一の述語名が使えない時の方便である。一般に、述語の意味する言葉を述語名とすることが望ましいが、行列述語などを含めて数学的なアルゴリズムでは、部分的な計算を言葉で表現することが困難な場合も多い。それでこのような述語の命名がしばしば見られる。行列の掛算(_行列_1,_行列_2,_行列の積) :-        行列の転置(_行列_2,_転置された行列),        行列の掛算_1(_行列_1,_転置された行列,_行列の積).行列の掛算_1([],_,[]) :- !.行列の掛算_1([L_1|R1],LL_2,[S1|R3]) :-        行列の掛算_2(L_1,LL_2,S1),        行列の掛算_1(R1,LL_2,R3).行列の掛算_2(_,[],[]) :- !.行列の掛算_2(L_1,[L_2|R2],[S|R3]) :-        行列の掛算_3(L_1,L_2,S),        行列の掛算_2(A,R2,R3).行列の掛算_3([],[],0) :- !.行列の掛算_3([A|R1],[B|R2],S) :-        S1 is A * B,        行列の掛算_3(R1,R2,S2),        S is S1 + S2.実行例?- 行列の掛算([[3,4,8],[2,6,5]],[[7,8],[2,6],[5,4]],X).X = [[69,80],[51,72]].
正方行列の右下がり対角要素リストと左下がり対角要素リストを得る。正方行列の対角要素とは、1、5、9 が右下がり対角要素であり、3、5、7 が左下がり対角要素であるとする。これを nth1 と length と append の組み合わせで定義する。右下がり対角要素リスト(_正方行列,_右下がり対角要素リスト) :-        findall(V,(                    nth1(_nth1,_正方行列,L),                    nth1(_nth1,L,V)),                _右下がり対角要素リスト).左下がり対角要素リスト(_正方行列,_左下がり対角要素リスト) :-        findall(V,(                    nth1(_nth1,_正方行列,L),                    length([_|R],_nth1),                    append(_,[V|R],L)),                _左下がり対角要素リスト),!.組込述語 nth1 は非決定性の述語で第一引数が論理変数の場合は、1、2、3、…、n とバックトラックされる度に順に値を生成する。nth1 の 1 は1からこのカウントを開始するの意味である。上記二つの定義では、要素位置を示す論理変数 _nth1 が現れるが、この論理変数に対して何ら演算を施してはいない。このように要素位置等の数値による管理からプログラマが解放される機会が多いことも Prolog の大きな特長である。実行例を示す。?- 右下がり対角要素リスト([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]],L).L = [1,6,11,16]?- 左下がり対角要素リスト([[1,2],[3,4]],L).L = [2,3]
行列がガウス行列であるかどうか検査します。ここではガウス行列検査/1を述語名を冗長に取って、その事によって、宣言的に述語定義することで、Prologのプログラムが、ガウス行列の解説になっているように工夫されています。ガウス行列検査(_ガウス行列) :-        'ガウス行列とは、行が下がるごとに、最初に現れる0でない要素が右に移っていく行列のことをいいます'(_ガウス行列).'ガウス行列とは、行が下がるごとに、最初に現れる0でない要素が右に移っていく行列のことをいいます'(_ガウス行列) :-        行が下がるごとに最初に現れる0でない要素が(_ガウス行列,_最初に現れる0でない要素の変位のリスト),        右に移っていく行列のことをいいます(_最初に現れる0でない要素の変位のリスト).行が下がるごとに最初に現れる0でない要素が(_ガウス行列,_最初に現れる0でない要素の変位のリスト) :-        findall(_最初に現れる0でない要素の変位,(                    append(_,[_ガウス行列の行|_],_ガウス行列),                    最初に現れる0でない要素の変位が(_ガウス行列の行,_最初に現れる0でない要素の変位)),                _最初に現れる0でない要素の変位のリスト).最初に現れる0でない要素の変位が(_ガウス行列の行,_最初に現れる0でない要素の変位) :-        append(_0のみのならび,[_0でない要素|_],_ガウス行列の行),        \+(_0でない要素 = 0),        length(_0のみのならび,_最初に現れる0でない要素の変位),!.右に移っていく行列のことをいいます([A]).右に移っていく行列のことをいいます([A,B|R]) :-        A < B,        右に移っていく行列のことをいいます([B|R]).実は、最初に現れる0でない要素がない、すなわち、全ての要素が0であるような行があり得ますが、_0でない要素がない訳ですからappend(_0のみのならび,[_0でない要素|_],_ガウス行列の行),が偽になります。これは、最終的に、行が下がるごとに最初に現れる0でない要素が/2の中のfindall/3の中に現れますから、偽になればリストLに採用されません。乃ち、この判定では全て要素が0の行は無視されます。このガウス行列検査/1のように、配列と違って添字を使わないリストで行列を表現するため、Prologの行列の処理では、大小比較以外の数値計算が全く現れないこともあります。
Prolog が集合をどのように扱うかについては、既に Prolog プログラミングの章で述べた。ある集合が別の集合の部分集合であるか確かめる述語 部分集合 を定義する。この述語は組込述語 subset として定義済みであり、その定義はsubset(_subsets,_sets) :-        forall(member(_element,_subsets),member(_element,_sets)).であると考えられる。_subsets のメンバーは必ず _sets のメンバーであると宣言している。組込述語 forall は第一引数の副目標が真になる場合は、第二引数の副目標も必ず真になると宣言するメタ述語である。「全ての・・・について、」がその意味と考えればよい。forall(member(_element,_subsets),member(_element,_sets))は「全ての部分集合の要素は、全体集合の要素である」という意味となる。これとは別に再帰を使った 部分集合 の定義もある。部分集合([],_).部分集合([_要素|R],_集合) :-        member(_要素,_集合),        部分集合(R,_集合).実行例を示す。?- 部分集合([2,4],[4,1,2,6]).true.?- 部分集合([3,4],[4,1,2,6]).false.となる。集合では「その要素が・・・」と語られることが常であるが、「その要素が」をProlog述語として表現したものがmember/2である。
階乗は整数の性質に含まれる数の連関を使うだけで計算できる数少ない例の一つである。Nの階乗を 階乗/2 として定義する。階乗(0,1) :- !.階乗(N,_階乗) :-        N_1 is N - 1,        階乗(N_1,_階乗_1),        _階乗 is _階乗_1 * N.ここでの階乗の定義のように、計算対象となる要素が、常に1ずつ減っていく、そして、それだけで計算が完了するというのは特別な例なのであって、そのような固定した性質がない集合の計算では計算対象をリストに取ることが多い。既にそのような例としては 加算の リスト要素の加算 があった。階乗には以下のように第二引数に累算部分を明示的に取る定義もある。階乗(N,_階乗) :-        階乗(N,N,_階乗).階乗(1,_階乗,_階乗) :- !.階乗(N,_階乗_1,_階乗) :-        N_1 is N - 1,        _階乗_2 is _階乗_1 * N,        階乗(N_1,_階乗_2,_階乗).この定義は最後に副目標として階乗(N_1,_階乗_2,_階乗)のような再帰表現の締め括りが来ている。このような形式の再帰を末尾再帰と言って、Prologに於いてはこの末尾再帰の方が、インタプリタ/コンパイラが最適化をしやすい。再帰の実行が深くなったり、巨大数を扱った場合、スタックオーバフローのようなエラーになることを回避しやすい。?- 階乗(100000,X).このような質問がなされた場合、上側の定義、則ち末尾再帰でない定義では多くの処理系で解が返らない。一方、末尾再帰の定義では、もちろん多倍長整数のサポートが条件ではあるが、456579桁の整数解が返るに相違ない。このような事情から、一般にPrologプログラマには二つの定義のうち、下の定義の方が好まれる。
10000以下の素数のリストをウィルソンの定理による素数判定を使って得る。この計算では階乗計算が繰り返し使われるのだが、述語に定義節を動的に書き加えることによって、階乗の呼び出し回数を大幅に少なくできる。ただし、述語論理を完全に逸脱したプログラムである。:- dynamic(階乗保存計算/2).'ウィルソンの定理を使って素数を判定する関数is_primeを実装し、100000以下の素数をリストに得る'(_10000以下の素数リスト) :-        findall(_p,(                    between(1,10000,_p),                    is_prime(_p)),                _10000以下の素数リスト).is_prime(_p) :-        'ウィルソンの定理とは pが素数 <=> (p-1)!+1 (mod p) == 0'(_p).'ウィルソンの定理とは pが素数 <=> (p-1)!+1 (mod p) == 0'(_p) :-        _p > 0,        Y is _p - 1,        階乗保存計算(Y,Z),        0 is (Z + 1) mod _p,!.階乗保存計算(0,1) :- !.階乗保存計算(1,1) :- !.階乗保存計算(N,X) :-        N2 is N - 1,        階乗保存計算(N2,Y),        X is N * Y,        asserta((階乗保存計算(N,X) :- !)).asserta は述語の先頭に定義節を加える組込述語。既に計算した階乗は答えを階乗保存計算の先頭に付け加えることで、以後階乗計算に入るまえに、その解を得ることができるようになる。述語 階乗保存計算 の定義は階乗保存計算(7). を実行前と後では以下のように変化する。?- listing(階乗保存定義/2).階乗保存計算(0, 1) :- !.階乗保存計算(1, 1) :- !.階乗保存計算(A, C) :-        B is A + -1,        階乗保存計算(B, D),        C is A * D,        asserta((階乗保存計算(A, C):-!)).true.?- 階乗保存計算(7,X).X = 5040?- listing(階乗保存計算/2).階乗保存計算(7, 5040) :- !.階乗保存計算(6, 720) :- !.階乗保存計算(5, 120) :- !.階乗保存計算(4, 24) :- !.階乗保存計算(3, 6) :- !.階乗保存計算(2, 2) :- !.階乗保存計算(0, 1) :- !.階乗保存計算(1, 1) :- !.階乗保存計算(A, C) :-        B is A + -1,        階乗保存計算(B, D),        C is A * D,        asserta((階乗保存計算(A, C):-!)).true.listing は現在の述語定義を示す、処理系のサービス述語である。組込述語 asserta や assertz を使えば、プログラムによるプログラムの生成が可能になる。それだけではなく、プログラムの実行中に追加プログラムコードを生成して、それを即実行することもできる。
リストの要素が重複している時、これを唯ひとつの要素に置き換えたい時がある。リストの重複要素を削除する([],[]).リストの重複要素を削除する([A|R1],R2) :-        member(A,R1),        リストの重複要素を削除する(R1,R2).リストの重複要素を削除する([A|R1],[A|R2]) :-        \+(member(A,R1)),        リストの重複要素を削除する(R1,R2).?- リストの重複要素を削除する([3,4,2,3,5],L).L = [4,2,3,5].最も基本的な再帰のなかで member/2 を使って後に再びこの要素が現るかどうか検査している。複数同一要素が存在するときには、最後の位置にある要素だけが選択される。リスト要素の順序の変化に注意が必要である。リストの重複要素削除には、他にも有力な方法がある。組込述語 setof/3 と member/2 を組合せて使う。setof/3は名前から想像できるように出来上がるリストを集合とみなす。従ってこの述語のなかには要素が重複したらこれを取り除いてしまう機能を含んでいる。リストの重複要素を削除する(L1,L2) :-        setof(_要素,member(_要素,L1),L2).もうひとつ、これは組込述語 sort/2 の約束事であるが、最終的に整列結果の重複要素は取り除かれる。従って、リストの重複要素を削除する(L1,L2) :-        sort(L1,L2).これだけで済んでしまう。
リスト要素が全て同じ。検査述語であると同時に、リストに変数を含む場合はそれを第二引数と単一化して全て同じ要素になるように企てる働きをする。さらに第二引数が変数で呼ばれたら、第一引数のリスト要素が全て同じ場合にのみ真となり、その要素と第二引数が単一化される。全ての要素が同じ([],_).全ての要素が同じ([A|R],A) :-        全ての要素が同じ(R,A).第一引数のリストのなかにひとつでも第二引数と異なった要素が現れたら、則ち偽となる。偽にならず、第一引数が[]まで到達したら、全ての要素は第二引数と同じであったことになる。実行例?- 全ての要素が同じ([2,2,2],2).true.?- 全ての要素が同じ([2,2,3],2).false.?- 全ての要素が同じ([2,2,2],X).X = 2?- 全ての要素が同じ([X,Y,Z],0).X = 0,Y = 0,Z = 0,?- 全ての要素が同じ([A,2,B],X).A = 2,B = 2,X = 2.最後の例は、Prologの単一化の = による制約表現とその制約解消過程が面白い。
上記、全ての要素が同じ/2は大変有用な述語であるが、仕様上重大な疑問がある。それは第一節の定義で、空リストの要素という矛盾を認めている点である。この問題は単に矛盾であるばかりでなく、?- append(L1,L2,[1,1,3,2]),全ての要素が同じ(L1,A).L1 = [], L2 = [1,1,3,2];L1 = [1], L2 = [1,3,2];L1 = [1,1], L2 = [3,2];false.?-となり、L1 = [] を解として含んでしまう。これは受け入れがたい。空リストになるかどうかの検査が常に必要になり不便でもある。そこで、'全ての要素が同じ(但し空リストは除く)'/2を定義しよう。定義は述語名そのまま、'全ての要素が同じ(但し空リストは除く)'(L,A) :-        全ての要素が同じ(L,A),        \+(L = []).と定義する。これで?- '全ての要素が同じ(但し空リストは除く)'([X,Y,Z],0).X = 0,Y = 0,Z = 0,?- '全ての要素が同じ(但し空リストは除く)'([A,2,B],X).A = 2,B = 2,X = 2.重複するから一部省略するが、全ての要素が同じ/2に於ける質問例の動作通りになる。なお、全ての要素が同じ/2を以下のように変更すると、全ての要素が同じ([A],A).全ての要素が同じ([A|R],A) :-        全ての要素が同じ(R,A).?- 全ての要素が同じ(L,3).L = [3];L = [3];L = [3,3];L = [3,3];・・・というような実行となり、思い通りの結果にならない。結局この定義に於いても、全ての要素が同じ([A],A).全ての要素が同じ([A|R],A) :-        \+(R=[]),        全ての要素が同じ(R,A).\+(R=[])という意味の判り難い副目標が必要ということになる。
N個の空白からなるアトムを生成する。述語名がシングルクォートで括られているのはその先頭文字に英大文字が来ているからである。ここでは組込述語 length によって要素数N個の変数のリストを生成し、その要素全てを空白文字とした上で、文字のリストからアトムを生成するために、組込述語 atom_chars を使っている。'N個の空白からなるアトムを生成する'(N,_アトム) :-        length(L,N),        全ての要素が同じ(L,' '),        atom_chars(_アトム,L).実例を示す。?- 'N個の空白からならアトムを生成する'(8,Atom).Atom = '        '上に示した定義は、全ての要素が同じ/2が既に用意されていることを前提に、空白からなるアトムを作ったが、'N個の空白からなるアトムを生成する'(N,_アトム) :-        findall(' ',between(1,N,_),L),        atom_chars(_アトム,L).でよく、この定義の空白と指定された部分を抽象して、同一文字からなるアトムの定義は'N個の同一文字からなるアトムを生成する'(N,_文字,_アトム) :-        findall(_文字,between(1,N,_),L),        atom_chars(_アトム,L).でよい。'N個の空白からなるアトムを生成する'/2の再帰的な定義は組込述語 atom_concat/3 を使って実現する。'N個の空白からなるアトムを生成する'(1,' ') :- !.'N個の空白からなるアトムを生成する'(N,_アトム) :-        N_1 is N - 1,        'N個の空白からなるアトムを生成する'(N_1,_アトム_1),        atom_concat(' ',_アトム_1,_アトム).上に示した非再帰的な定義とどちらが判りやすいか、取捨に悩むことが多い。
事務計算などでは、123という整数を8桁の数値表現で、しかも頭部を空白ではなく0で埋めることを要求されることがある。これを上記 全ての要素が同じ を使って定義する。最初に枠を取り、頭部の桁不足の部分は 全ての要素が同じ を使って 0 を埋めている。ヘッドゼロサプライ(_桁数,_数値,_ヘッドゼロサプライ数値表現) :-        length(_桁数枠のリスト,_桁数),        number_chars(_数値,_数字のリスト),        append(_頭部の枠リスト,_数字のリスト,_桁数枠のリスト),        全ての要素が同じ(_頭部の枠リスト,'0'),        atom_chars(_ヘッドゼロサプライ数値表現,_桁数枠のリスト).number_chars、atom_chars ともに組込述語で、それぞれ、数値を分解して数字リストに、アトムを分解して文字リストとする。頭部の枠リストの桁（要素数）は述語 append が決定する。実行例?- ヘッドゼロサプライ(8,123,X).X = '000000123'?-ヘッドゼロサプレスの定義は、ヘッドゼロサプレス(_桁数,_数値,_ヘッドゼロサプレス数値表現) :-        length(_桁数枠のリスト,_桁数),        number_chars(_数値,_数字のリスト),        append(_頭部の枠リスト,_数字のリスト,_桁数枠のリスト),        全ての要素が同じ(_頭部の枠リスト,' '),        atom_chars(_ヘッドゼロサプレス数値表現,_桁数枠のリスト).変数名を変更したが実質的には 全ての要素が同じ の第二引数を変更するだけの違いである。
以下二つとも事務計算では必須の述語である。最初の述語が必要になるのは帳票からOCRで文字列を読み取りデジタルテキスト化されたものが入力になる場合であろう。カンマ区切り文字列を数値に変換(_文字列,_数値) :-        findall(_カンマではない文字,            カンマではない文字(_文字列,_カンマではない文字),_数字文字ならび),        number_chars(_数値,_数字文字ならび).カンマではない文字(_文字列,_文字) :-        sub_atom(_文字列,_,1,_,_文字),        \+(_文字=',').整数を3桁ずつカンマ区切りした文字列に変換(_数値,_数値文字列) :-        整数を3桁ずつアトムに変換しながら区切る(_数値,[],L),        atomic_list_concat(L,',',_数値文字列).整数を3桁ずつアトムに変換しながら区切る(0,L,L) :- !.整数を3桁ずつアトムに変換しながら区切る(N,L1,L) :-        整数の下位3桁を文字列に変換する(N,_桁数を調整した数値文字列,_下位3桁を取り除いた整数),        整数を3桁ずつアトムに変換しながら区切る(_下位3桁を取り除いた整数,[_桁数を調整した数値文字列|L1],L).整数の下位3桁を文字列に変換する(N,_桁数を調整した数値文字列,_下位3桁を取り除いた整数) :-        _Nを1000で割った剰余 is N mod 1000,        _下位3桁を取り除いた整数 is N // 1000,        atom_number(_数値文字列,_Nを1000で割った剰余),        桁を3桁に(_下位3桁を取り除いた整数,_数値文字列,_桁数を調整した数値文字列).桁を3桁に(0,A,A) :- !.桁を3桁に(_,A,A) :-        atom_length(A,3),!.桁を3桁に(_,A,C) :-        atom_concat('000',A,B),        sub_atom(B,_,3,0,C).カンマ区切り文字列を数値に変換/2はnumber_chars/2に寄り掛かった定義になっている。整数を3ケタカンマ区切り数値文字列に変換/2は、ここでは整数に限定しているが、実数を対象にする場合は、整数部と少数部に分離し、整数部にだけこの述語を適用すればよい。整数を3桁ずつアトムに変換しながら区切る/3に於いて、Lに下3桁ずつ、積んで行き、しかも最終的に上位桁から下位の順に展開できている。これは、プログラム事例の後に出てくるリスト要素の反転の中で見ることができるPrologの特徴的な技法である。整数の下位3桁を文字列に変換する/3はカンマ区切りの厄介なところで、1000で除した剰余が2桁以下の時に頭部に0を強制している。しかし、最上位の3桁はその限りではない。そのための述語が桁を3桁に/3である。
ユークリッドの互除法によって最大公約数を求める。数値演算の場合、他の言語とそれほど変わらない。Prologの特徴を求めるならば出力用の引数が必要とされることだろう。最大公約数(N,_最大公約数,_最大公約数) :-        0 is N mod _最大公約数.最大公約数(N,M,_最大公約数) :-        M_2 is N mod M,        最大公約数(M,M_2,_最大公約数).実行例?- 最大公約数(49,28,X).X = 7?-
n以下の全ての素数をリストに集める。エラトステネスの篩を述語として定義し、これを呼び出す。最初の述語 n以下の素数 の冒頭で、2から始まりnまで連続する整数のリストを組込述語 findall と between を使って生成する。代表的な生成パターンである。このリストを対象に小さい順に素数を探し、その素数の倍数をリストから削除して再帰的にエラトステネスの篩は実行される。n以下の素数(_n以下,_n以下の全ての素数) :-        findall(_数,between(2,_n以下,_数),_2以上_n以下の数リスト),        エラトステネスの篩(_2以上_n以下の数リスト,_n以下の全ての素数).エラトステネスの篩([],[]) :- !.エラトステネスの篩([A|R1],[A|R2]) :-        エラトステネスの篩(A,R1,L),        エラトステネスの篩(L,R2).エラトステネスの篩(_,[],[]) :-!.エラトステネスの篩(N,[A|R1],R2) :-        0 is A mod N,        エラトステネスの篩(N,R1,R2),!.エラトステネスの篩(N,[A|R1],[A|R2]) :-        エラトステネスの篩(N,R1,R2).エラトステネスの篩 の定義の中で findall　を使うと エラトステネス/3 は実は不要である。エラトステネスの篩([],[]).エラトステネスの篩([M|R1],[M|R2]) :-        findall(N,(                    member(N,R1),                    \+(0 is N mod M)),                L),        エラトステネスの篩(L,R2).どちらの定義が読みやすいかについては、常に問題となる。この findall を エラトステネスの篩 の中に持つエラトステネスの篩([],[]) :- !.エラトステネスの篩([M|R1],[M|R2]) :-        エラトステネスの篩(M,R1,L),        エラトステネスの篩(L,R2).エラトステネスの篩(M,R1,L) :-        findall(N,(                    member(N,R1),                    \+(0 is N mod M)),                L).が最も宣言的なエラトステネスの篩の定義かも知れない。エラトステネスの篩/2 の方は、findall で生成される新たなリストが第一引数に置き換えられて再び駆動される。このような新しい対象を生成しつつ、ダイナミックに繰り返すパターンは、再帰的な定義以外に方法がない。実行例?- n以下の素数(32,L).L = [2,3,5,7,11,13,17,19,23,29,31]?- エラトステネスの篩([2,3,4,5,6,7,8,9,10,11,12,13,14],L).L = [2,3,5,7,11,13]?-
Prologでは、文字列という場合、一般にアトムを指すが、String（文字コードのリスト）を指す場合もあり、少々曖昧である。ここではアトムを指すとする。文字列の検索(_文字列,_検索語,_検索語の前方文字列,_検索語,_検索語の後方文字列) :-        sub_atom(_文字列,_開始点,_文字列長,_残り文字列長,_検索語),        sub_atom(_文字列,0,_開始点,_,_検索語の前方文字列),        sub_atom(_文字列,_,_残り文字列長,0,_検索語の後方文字列).sub_atom という極めてスーパーな非決定性の組込述語がこの機能の全てを司る。開始点は0オリジンであることに注意が必要である。Prologの組込述語では1オリジンを使うものが多いのだが、この述語は0オリジンである。sub_atom の仕様は (1) 第一引数に検索対象アトムがくる、(2) 第二引数には検索語の開始点がくる、(3) 第三引数には検索語の文字数、(4) 第四引数には検索が成功した時の残り文字列長、(5) 第五引数に検索語がくる。sub_atomは文字の出現順序は保たれるが、対象文字列を一文字ずつ開始点、終了点のポインターをずらしながら試行錯誤で、全ての切り取ることができる副文字列が試さながら実行される。従って、対象文字列が長く、第二、第三、第四引数が変数の場合は、検索を完了するまでに時間を要する。文字列の検索にはsub_atom/5の代わりに組込述語のatom_concat/2を使う定義もある。こちらの方が引数に変位が現れず若干は抽象的な定義である。文字列の検索(_文字列,_検索語,_検索語の前方文字列,_検索語,_検索語の後方文字列) :-atom_concat(_検索語の前方文字列,_残り文字列,_文字列),atom_concat(_検索語,_検索語の後方文字列,_残り文字列).atom_concat/2は二つのアトムを結合することと、アトムを二つの文字列に分解すること、この二つ意味を双方向に持っている。実行例?- 文字列の検索(abd126fgabyz,ab,X,Y,Z).X = '',Y = ab,Z = d126fgabyz;X = abd126fg,Y = ab,Z = z;False上記検索パターン通じて、目標に _検索語 がふたつ冗長に現れ、単一化を二重に行っている部分もあるが、将来、検索語に何らかの記号パターン(正規表現のような)が利用される可能性を考えて、ここではあえて検索語と検索結果の検索語が同じになることを承知の上で、一引数余分に確保している。検索語に変数がきたらどうなるか。ここではYとする。この場合、文字列検索/5は検索文字列の候補を挙げてくるだけである。その後に連接した二つのsub_atom/5で制限を付けている。?- 文字列の検索(abd126fgabyz,Y,X,Y,Z),sub_atom(Y,0,2,_,fg),sub_atom(Y,_2,_,by).X = abd126,Y = fgaby,Z = yz.このように検索語を与えなくても、検索する可能性を持つことはPrologによる文字列処理の特長である。sub_atomは第一引数が変数で実行されるとエラーとなる。第二引数以下の情報から双方向に第一引数を生成することはしない。?- sub_atom(A,0,5,0,abcde).ERROR: sub_atom/5: Arguments are not sufficiently instantiated論理的には第一引数Aにabcdeが返ってきても良さそうなケースだが、述語定義の仕様から、エラーとなってしまう。
ISO規格を含めて、ほとんどの処理系では正規表現がサポートされていない。Prologは文字列操作を得意とする言語だが、それでも複雑な置換パターンでは長い定義となることが多い。最初に、置換対象が一つの単純な置換を考えてみよう。文字列の置換(_対象文字列,_置換される副文字列,_置換する副文字列,_置換された文字列) :-        sub_atom(_対象文字列,_開始点,_長さ,_残り長さ,_置換される副文字列),        sub_atom(_対象文字列,0,_開始点,_,_前文字列),        sub_atom(_対象文字列,_,_残り長さ,0,_後文字列),        atomic_list_concat([_前文字列,_置換する副文字列,_後文字列],_置換された文字列).atomic_list_concat はリスト要素を結合して新しいアトムを生成する。実行例を示す。?- 文字列の置換(いろははほへと,はは,はに,_置換された文字列)._置換された文字列 = いろはにほへとこれはうまく行くが、複数置換対象が存在する場合を見てみよう。?- 文字列の置換(生垣作るその生垣を,生垣,八重垣,_置換された文字列)._置換された文字列 = 八重垣作るその生垣を;_置換された文字列 = 生垣作るその八重垣を;false.置換対象が複数あっても、一ヶ所だけ置換するという場合もある。その場合には選択的に置換できるこの定義で良い。しかしこの例もそうだが、対象となる副文字列全てを置換したいことも多い。結論を言ってしまえば、このようなバックトラックを使ったパターン(失敗駆動)では全置換は定義できない。文字列の全置換(_対象文字列,_置換される副文字列,_置換する副文字列,_置換された文字列) :-        置換対象の選択(_対象文字列,_置換される副文字列,_前文字列,_後文字列),        文字列の全置換(_後文字列,_置換される副文字列,_置換する副文字列,_置換された後文字列),        atomic_list_concat([_前文字列,_置換する副文字列,_置換された後文字列],_置換された文字列),!.文字列の全置換(_文字列,_,_,_文字列).置換対象の選択(_対象文字列,_置換される副文字列,_前文字列,_後文字列) :-        sub_atom(_対象文字列,_開始点,_長さ,_残り長さ,_置換される副文字列),        sub_atom(_対象文字列,0,_開始点,_,_前文字列),        sub_atom(_対象文字列,_,_残り長さ,0,_後文字列).一般に置換では、置換対象文字列が存在しなかった時、その副目標(質問)を偽としないで、元の文字列をそのまま残す。文字列の全置換 の第二節 文字列の全置換(_文字列,_,_,_文字列). はそのために必要である。ちょっとわかりにくいが、文字列の全置換 は再帰的な述語である。置換対象までとその後文字列に分割して、後文字列を再帰的に置換したものと、それまでの文字列を置換しながら結合する。実行例?- 文字列の全置換(生垣作るその生垣を,生垣,八重垣,_置換された文字列)._置換された文字列 = 八重垣作るその八重垣をこれで二ヶ所の生垣を八重垣に置換することができる。
リストの結合とは引数として与えられた二つのリストを最初のリストの最終要素の次から第二リストの最初の要素から順に付け加えて行って、一つのリストに纏めることを言う。この述語はほとんどの処理系で組込述語 append として特に利用者が定義しなくても済むが、述語定義技法としての観点からも Prolog を代表する述語であるため、ここでは append 述語が Prolog でどのように定義されるかを紹介する。リストの結合(L1,L2,L) :- append(L1,L2,L).appendは2つのリストを結合するappend([],L,L).append([E|L1],L2,[E|L]) :- append(L1,L2,L).実行例?- append([a,b],[1,2,3],L).L = [a,b,1,2,3]appendの意味は結合に留まらない。第一引数、第二引数に変数が来るとリストを分解する。実は非決定性の述語としての代表でもある。?- append(X,[2,3],[1,2,3]).X = [1]?- append(X,Y,[1,2,3]).X = [],Y = [1,2,3];X = [1],Y = [2,3];X = [1,2],Y = [3];X = [1,2,3],Y = [];falseとなる。さらに以下のように使用するとappendは実はmemberのスーパーセットであることがわかる。?- append(L1,[X|L2],[ワカメ,マスオ,タラオ]).L1 = [],X  = ワカメ,L2 = [マスオ,タラオ];L1 = [ワカメ],X  = マスオ,L2 = [タラオ];L1 = [ワカメ,マスオ],X  = タラオ,L2 = [];falseここで注目するべきことは、このような使い方の append に於いては、切り出したい情報とその情報のリスト前部、リスト後部の情報を同時に取得できることである。例えば切り出した情報（上の定義例では X）の前部や後部にXが含まれていないか検査などが可能になる。これは member においては不可能なことである。append は多義的な述語であり、同時に Prolog を代表する述語でもあるため、機能に見合った述語名をリストの結合、リストの分解という具合に与えるか、それとも、通りのよい append 一本で貫くか迷うことが多い。ここではこの述語定義を理解、記憶してもらうためにもっぱら append で通したが、それぞれ別の述語名を与えて利用するのが本来の Prolog の姿であろう。リストの結合(L1,L2,L) :- append(L1,L2,L).リストの分解(L1,L2,L) :- append(L1,L2,L).
引数が3のappendを示したが、4引数以上のものも便利である。?- append(L1,[X,Y],L3,[a,b,c,d]).L1 = [],X = a,Y = b,L3 = [c,d];L1 = [a],X = b,Y = c,L3 = [d];L1 = [a,b],X = c,Y = d,L3 = [];false.このように、中間のリストの前のリスト、後のリストという分解が簡単にできる。この append/4 の定義は append/3 の定義を利用してappend([],L2,L3,L4) :-        append(L2,L3,L4).append([E|L1],L2,L3,[E|L4]) :-        append(L1,L2,L3,L4).である。さらに append/4 ができれば、 append/5 はappend([],L2,L3,L4,L5) :-        append(L2,L3,L4,L5).append([E|L1],L2,L3,L4,[E|L5]) :-        append(L1,L2,L3,L4,L5).となっていく。一引数少ない append の定義ができていれば、このように、それを第一節の本体に使って簡単に定義を追加できる。
四引数のappendの定義ができたところで、これを使って二文字以上の要素が昇順に並ぶリストを検索する。この探索は、対象がアトムではなくリストになる点で、member/2,append/3,select/3のそれに比べで強力である。二文字以上の要素が昇順に並ぶリストを検索する(_文字リスト,_前リスト,_二文字以上の要素が昇順に並ぶリスト,_後リスト) :-        append(_前リスト,_二文字以上の要素が昇順に並ぶリスト,_後リスト,_文字リスト),        要素が昇順に並んでいる(_二文字以上の要素が昇順に並ぶリスト).要素が昇順に並んでいる([A,B]) :-        A @=< B.要素が昇順に並んでいる([A,B|R]) :-        A @=< B,        要素が昇順に並んでいる([B|R]).append([],L2,L3,L4) :-        append(L2,L3,L4).append([U|L1],L2,L3,[U|L4]) :-        append(L1,L2,L3,L4).第二引数のリストが単純な性格を持ち、その性質が完結しているか検査する述語を書くような場合であるが、その程度の難度の検索ではappend/4は強力である。次に二文字以上の要素が昇順に並ぶのだが、各昇順文字リストのグループとしては最長の文字リストを検索する。二文字以上の要素が昇順に並ぶリストを検索する(_文字リスト,_前リスト,_二文字以上の要素が昇順に並ぶリスト,_後リスト) :-        append(L1,L2,L3,_文字リスト),        \+((last(_前リスト,A),_二文字以上の要素が昇順に並ぶリスト=[B|_],A @< B)),        \+((last(_二文字以上の要素が昇順に並ぶリスト,C),_後リスト=[D|_],C @< D)),        要素が昇順に並んでいる(_二文字以上の要素が昇順に並ぶリスト).last([A],A) :- !.last([A|R],B) :-        last(R,B).要素が昇順に並んでいる([A,B]) :-        A @=< B.要素が昇順に並んでいる([A,B|R]) :-        A @=< B,        要素が昇順に並んでいる([B|R]).last/2は第一引数のリストの最終要素が第二引数と単一化される。ここでは最後の要素と次のリストの先頭要素を比較している。検索文字列が先頭からだったり、末尾まで続いている場合には、第一引数や第三引数が[]になる。last/2は偽となりそれぞれ、        \+((last(_前リスト,A),_二文字以上の要素が昇順に並ぶリスト=[B|_],A @< B)),        \+((last(_二文字以上の要素が昇順に並ぶリスト,C),_後リスト=[D|_],C @< D)),それを否定しているから、ここの検査条件は真になる。
先に文字列の検索を組込述語 sub_atom を使うことで例題とした。ここでは一旦、アトムとしての文字列を文字を要素とするリストに変換して検索する例を示す。この場合、検索語も文字のリストに変換する。文字列の検索(_文字列,_検索語,_検索語の前方文字列,_検索語,_検索語の後方文字列) :-        文字列と検索語を文字リストに変換(_文字列,_検索語,_文字リスト,_検索文字リスト),        文字リストの検索(_文字リスト,_検索文字リスト,_検索語の前方文字リスト,_検索文字リスト,_検索語の後方文字リスト),        '検索語の前方文字リスト、検索文字リスト、検索語後方の文字リストを文字リストから文字列に変換'(_検索語の前方文字リスト,_検索文字リスト,_検索語の後方文字リスト,_検索語の前方文字列,_検索語,_検索語の後方文字列).文字列と検索語を文字リストに変換(_文字列,_検索語,_文字リスト,_検索文字リスト) :-        atom_chars(_文字列,_文字リスト),        atom_chars(_検索語,_検索文字リスト).文字リストの検索(_文字リスト,_検索文字リスト,_検索語の前方文字リスト,_検索文字リスト,_検索語の後方文字リスト) :-        append(_検索語の前方文字リスト,L2,_文字リスト),        append(_検索文字リスト,_検索語の後方文字リスト,L2),'検索語の前方文字リスト、検索文字リスト、検索語後方の文字リストを文字リストから文字列に変換'(_検索語の前方文字リスト,_検索文字リスト,検索語の後方文字リスト,_検索語の前方文字列,_検索語,_検索語の後方文字列) :-        atom_chars(_検索語の前方文字列,_検索語の前方文字リスト),        atom_chars(_検索語,_検索文字リスト),        atom_chars(_検索語の後方文字列,_検索語の後方文字リスト).「検索語の前方文字リスト、検索文字リスト、検索語の後方文字リストを文字リストから文字列に変換」がシングルクォートで囲まれて定義されているのは、途中に「、」が含まれているからである。規格で定められてはいないが、全角の記号は将来全角文字のみで処理系が利用される可能性から、記号扱いにしている処理系が多い。述語 文字リストの検索/5 では、上記のリストの結合 append が、リストの結合というより分解として二つ連続して利用されている。
すでに文字列の検索を sub_atom を用いた例を示したが、一般に文字列の検索は atom_chars を用いて一旦文字のリストに変換してから検索するほうが定義が柔軟になる。:- dynamic(パターン照合/2).文字リストに変換しての検索(_文字列,_検索パターンリスト,_前文字列,_適合文字列,_後文字列) :-        atom_chars(_文字列,_文字リスト),        リストによる検索(_前文字列リスト,_適合文字リスト,_後文字リスト,_文字リスト),        パターン照合(_検索パターンリスト,_適合文字リスト),        atom_chars(_前文字列,_前文字リスト),        atom_chars(_適合文字列,_適合文字リスト),        atom_chars(_後文字列,_後文字リスト).リストによる検索([],L2,L3,L4) :-        append(L2,L3,L4).リストによる検索([A|R1],L2,L3,[A|R4]) :-        リストによる検索(R1,L2,L3,R4).この述語の利用者は、検索する前に述語 パターン照合/2 を定義する。リストによる検索/4 は append/4 として知られる述語。append/3 を member/2 として使う?- append(L1,[A|L2],[1,2,3,4]).に極めて近いが、Aは単項としか単一化できないのに対して、 リストによる検索/4 のL2は複数項のパターンを切り取ることができる点が違う。実例を示す。"八重"から始まり"に"で終わる文字列を検索する。?- assertz((パターン照合(L,L) :-        L = [八,重|R],append(L1,[に],R))).?- 文字リストに変換しての検索(八雲立つ出雲八重垣妻籠みに八重垣作るその八重垣を,L,_前文字列,_適合文字列,_後文字列)._前文字列 = 八雲立つ出雲,L = [八,重,垣,妻,籠,み,に],_適合文字列 = 八重垣妻籠みに,_後文字列 = 八重垣作るその八重垣を;false.ここでは検索パターンリストに変数を置いている。最初に[八,重]が来て文字がわからないリストが来て、そして最後に[に]が来る。Prolog では、このようにパターンを一つのリストで表現することはできない。それでここは変数にして、パターン照合 述語に解決を委ねている。
append を利用してリスト要素の隣を定義してみよう。極めて宣言的な定義となる。リスト要素の隣(_リスト,_要素,_隣の要素) :-        append(_,[_隣の要素,_要素|_],_リスト).リスト要素の隣(_リスト,_要素,_隣の要素) :-        append(_,[_要素,_隣の要素|_],_リスト).さらに、リスト要素の両隣はリスト要素の両隣(_リスト,_要素,_隣の要素_1,_隣の要素_2) :-        append(_,[_隣の要素_1,_要素,_隣の要素_2|_],_リスト).リスト要素の両隣(_リスト,_要素,_隣の要素_1,_隣の要素_2) :-        append(_,[_隣の要素_2,_要素,_隣の要素_1|_],_リスト).実行例?- リスト要素の隣([a,b,c],X,Y).X = a,Y = b;X = b,Y = a;X = b,Y = c;X = c,Y = b?- リスト要素の隣([a,b,c,d,e,f,g],b,f).false.?- リスト要素の隣([a,b,c,d,e,f,g],e,d).true.?- リスト要素の両隣([a,b,c,d,e,f,g],e,Y,Z).Y = d,Z = f;Y = f,Z = d二つの隣要素が第三引数と第四引数に出現順に入るのだと決めておけば定義はリスト要素の両隣(_リスト,_要素,_左隣の要素,_右隣の要素) :-        append(_,[_左隣の要素,_要素,_右隣の要素|_],_リスト).となる。これが自然な定義であろう。しかし、「リスト要素の両隣」がこの述語の仕様であったとすると、左右の順序や出現順はどこにも示唆されていないと考えることがむしろ素直であり、上記の左右順あるいは右左順の二節とする定義も成立するのである。
リストの要素の反転は reverse が組込述語になっているが、ここでは、これを定義してみる。リスト要素の反転([],[]).リスト要素の反転([A|R1],L) :-        リスト要素の反転(R1,L2),        append(L2,[A],L).append を使った明解な宣言性の強い定義であるが、実行速度が遅いことからこの定義はあまり使わることがない。普通、リストの反転の定義には、以下のように二つの述語に分解して定義する。ただしこの定義は完全ではない。正しい定義は最後に示す。リスト要素の反転(L1,L2) :-        リスト要素の反転(L1,[],L2).リスト要素の反転([],L,L).リスト要素の反転([A|R1],L1,L) :-        リスト要素の反転(R1,[A|L1],L).リスト要素の反転 の方の第二節 A に着目して欲しい。第二引数で受け取ったリストの前に追加しているが、最初が [] だから、先頭から順に末尾から付加されていくことになる。第一節の主張は、第一引数が [] になった時には、第二引数に反転したリストが積み上がっているはず、ということである。実行例?- リスト要素の反転([a,b,c],L).L = [c,b,a]さらに、?- リスト要素の反転(L,[a,b,c]).L = [c,b,a]述語の双方向性も確かめられたと思いがちだが、そうはいかない。?- リスト要素の反転(L,[a,b,c]).L = [c,b,a];%%%  解を示さなくなり、やがて、多くの処理系で内部メモリが足りなくなりエラーとなる(スタックオーバーフロー) %%%エラー状態が生成されていく過程は興味深いのだが、複雑で難解になり過ぎるため、どのような経過で内部メモリがなくなっていくかはここでは示さない。このような基礎的な述語定義の中に重大なエラーが潜む余地があることは Prologの弱点 と考えるべきである。以下はこのエラーに対する対策の一例である。リスト要素の反転(L1,L2) :-        リスト要素の反転(L1,[],L2).リスト要素の反転([],L,L) :- !.リスト要素の反転([A|R1],L1,L) :-        リスト要素の反転(R1,[A|L1],L).リスト要素の反転/3 の方の第一節にカットを入れる。これで上記エラーは回避できる。最初に示した append を利用した定義ではこのようなことは起こらない。それで、あまり使われることがないとしながらも、こちらの定義を最初に載せた。
"文字列" という文字列を反転して "列字文" という文字列を生成する 文字列の反転/2 を定義する。組込述語 atom_chars と上記定義した リストの反転 を組み合わせる。文字列の反転(_文字列,_反転した文字列) :-        atom_chars(_文字列,_文字のリスト),        リストの反転(_文字のリスト,_反転した文字のリスト),        atom_chars(_反転した文字列,_反転した文字のリスト),ここでは一旦文字のリストに変換している。それによってリストの反転/2が利用できた。ただし、元の文字列の形式に atom_chars をもう一度使って戻さなくてはならない。atom_chars でリストに変換せずに、文字列の反転/2を定義できるが、以下のような難しい定義となる。文字列の反転(_文字列,_反転した文字列) :-        文字列の反転(0,_文字列,_反転した文字列).文字列の反転(N文字目,_文字列,_文字) :-        sub_atom(_文字列,N文字目,1,0,_文字).文字列の反転(N文字目,_文字列,_副文字列) :-        sub_atom(_文字列,N文字目,1,_,_反転した文字列),        N_1文字目 is N文字目 - 1,        文字列の反転(N_1文字目,_文字列,_反転した文字列_1),        atom_concat(_反転した文字列_1,_文字,_反転した文字列).組込述語 atom_concat が使われた。アトムとアトムを結合して別の長いアトムを生成する述語である。文字列の反転の例を示す。?- 文字列の反転(文字列,X).X = 列字文?-