これが私の見解です。それは本質的に@RdRが持っているものであり、ロジックをより多くの述語に分割し、who()
主な述語のオーバーロードを少なくしました。
name(lily). % (1)
name(jack).
name(daisy).
country(italy).
country(usa).
country(germany).
hobby(football).
hobby(cooking).
hobby(reading).
grade(1).
grade(2).
grade(3).
student(N,C,H,G):- % (2)
name(N), country(C), hobby(H), grade(G).
permute(P,X,Y,Z):- (4)
call(P,X), call(P,Y), call(P,Z) % (6)
, X\=Y, Y\=Z, X\=Z.
students(A,B,C):- (3)
permute(name,N1,N2,N3) % (5)
, permute(country,C1,C2,C3)
, permute(hobby,H1,H2,H3)
, permute(grade,G1,G2,G3)
, A = student(N1,C1,H1,G1) % (7)
, B = student(N2,C2,H2,G2)
, C = student(N3,C3,H3,G3)
.
who(A,B,C):- % (8)
students(A,B,C)
, A = student(lily,C1,H1,G1) % (9)
, B = student(jack,C2,H2,G2)
, C = student(daisy,C3,H3,G3)
, C2 = germany % (10)
, H3 = cooking
, (( C2=italy -> G1 < G2) % (11)
;( C3=italy -> G1 < G3))
, (( H1=reading -> G2 < G1)
;( H3=reading -> G2 < G3))
, (( H1=football -> G1 < G2, G1 < G3)
;( H2=football -> G2 < G1, G2 < G3)
;( H3=football -> G3 < G1, G3 < G2))
.
% Running it:
% ?- who(A,B,C).
% A = student(lily, usa, reading, 2),
% B = student(jack, germany, football, 1),
% C = student(daisy, italy, cooking, 3) ;
% false.
討論
したがって、ここではかなりのことが行われており (これに興味をそそられました)、別の方法で行うことができるいくつかの選択があります (したがって、おそらく @RdR のソリューションとは対照的です)。
- 他の人が指摘したように、1 つの側面は、問題の説明で与えられた情報をエンコードする方法です。非常に具体的に表現することも (このケースだけを解決する)、より一般的に表現することもできます (たとえば、問題を 3 人以上の生徒に拡張できるようにするため)。
- この問題が同様の他の問題と異なるのは、1 人の学生に影響する制約 (「ジャックはドイツから来ました」)、2 人の学生に影響する制約 (「リリーの成績はイタリアの成績よりも良い」)、または次のような制約が混在していることです。それらすべて(「サッカーが好きな人が最高の成績を持っています」)。
- さらに、選言制約があります(「彼らはすべて異なる国から来ており、趣味も異なります」)。Prolog は、ファクトのすべての可能なインスタンスを調べるのに非常に優れていますが、1 つのインスタンスを選択して、述語の次の呼び出しのためにこれを除外するのはより複雑です。これにより、ペアごとに異なるファクトから一連の値を取得する方法を見つける必要があります。(例えば、Prolog が Lily の趣味が読書であると決めた場合、Jack の趣味として読書を割り当ててはなりません)。
- したがって、すべての既知の事実とそれらの可能な値 (1) をリストした後、最初に述語
student/4
(2) を定義して、生徒がこれら 4 つのプロパティを持っていることを単純に述べました。これにより、学生とその属性のすべての可能な組み合わせが生成され、学生全員が同じ名前を持ち、同じ国 (asf) から来ることも可能になります。
- これは、Prolog で大きすぎる結果セットを作成し、それをさらに絞り込む方法の良い例です (他の誰かが書いたように)。さらなる述語は、この「ジェネレーター」を利用して、その結果セットからさらに多くのソリューションをフィルタリングできます。これはテストも簡単で、各ステージで中間出力が意味をなすかどうかを確認できます。
- 次の述語
students/3
(3) では、前に述べたことを正確に試して、少なくとも同じ属性を 2 回使用しない (同じ趣味を持つ 2 人の学生など) 学生インスタンスを作成します。これを実現するには、すべての属性ファクト (name/1、country/1、...) を実行し、それぞれに対して 3 つの値を取得して、ペアごとに異なることを確認する必要があります。
- 属性の名前を除いて実装が常に同じである各属性に対してこれを明示的に行う必要がないように
permute/4
、属性名を渡すことができ、属性を検索するヘルパー述語 (4) を作成しました。事実として 3 回、バインドされた値がすべて同じではないことを確認します。
- したがって
permute(name,N1,N2,N3)
、students/3
(5) を呼び出すと、ルックアップcall(P,X), call(P,Y), call(P,Z)
(6) が実行され、 を呼び出すのと同じ結果になりname(X), name(Y), name(Z)
ます。(同じ属性の常に 3 つのファクトから 3 つの異なる値を収集しているため、これは実質的に 3 つの値セットの 3 つの順列を実行することと同じであるため、ヘルパー述語の名前が付けられています。)
- (7) に到達すると、生徒の属性ごとに個別の値があることがわかり、それらを 3 つの生徒インスタンスに分散するだけです。(これは実際には
student/4
述語なしで同じように機能するはずです。これは、Prolog でこのような構造化された用語をいつでもオンザフライで作成できるためです。student
述語を使用すると、"student(lily, 23 、asdf、-7.4)".)
- したがって
:- students(A,B,C).
、関連する属性を 2 回使用せずに、3 人の学生とその属性の可能な組み合わせをすべて生成します。良い。また、(より難しい)student()
構造を便利な 1 文字の変数でラップすることで、インターフェイスをよりクリーンにします。
- しかし、これらの素性制約を除けば、他の制約は実装していません。
who/3
これらは、(あまり洗練されていない)述語 (8)の後に続くようになりました。これは基本的students/3
にジェネレーターとして使用し、さらに制約を追加することで不要なソリューションをすべて除外しようとします (したがって、基本的には と同じシグネチャーを持ちstudents/3
ます)。
student
個々のインスタンスをフィルタリングするだけでなく、それらを個別に ("デイジー"、"ジャック" など) およびそれぞれの属性 ("デイジーの趣味" など) を参照できるようにする必要があるため、ここで別の興味深い部分が始まります。 )。したがって、結果変数 A、B、および C をバインドする際に、特定の名前でパターン マッチを行います。したがって、(9) のリテラル名lily
asfjack
です。これにより、Lily が 1 番目、2 番目、または 3 番目 (students/3
このような順列を生成するため) になるケースを考慮する必要がなくなります。そのため、その順序にない 3 セットの生徒はすべて破棄されます。
N1 =
lily
後でasfのような明示的な制約でこれを行うこともできました。ジャックはドイツ出身で、デイジーは料理が好きだという単純な事実を強調するために、私はそうします (10)。それらが失敗すると、Prolog は への最初の呼び出しに戻り、students/3
別の生徒のセットを取得するために試行できます。
- ここで、リリーの成績、ジャックの成績、およびサッカー愛好家の成績に関する追加の既知の事実に従ってください (11)。これは特に醜いコードです。
- 1 つには、「属性 X を持つ学生」というクエリに対する回答を返すことができるヘルパー述語があればいいでしょう。現在選択されている生徒 A、B、C、属性名 (「country」)、および値 (「italy」) を取得し、適切な生徒を返します。そのため、2 番目または 3 番目の学生である必要があると仮定するのではなく、イタリアからの学生をクエリすることができます (問題の説明では、Lily 自身はイタリア出身ではないことが示唆されています)。
- したがって、この架空のヘルパー述語を呼び出すと
student_from_attribute
、名前で学生構造の値を見つけて対応する値を返す別のヘルパーが必要になります。名前でその中のフィールドにアクセスできる、ある種のオブジェクト/名前付きタプル/レコードをサポートするすべての言語で簡単です。しかし、バニラのプロローグはそうではありません。そのため、頭のてっぺんから引き離すことができない何かを持ち上げる必要があります。
- また、 「成績」など、
who/3
から返された学生とは異なる属性が必要になるため、述語はこの他のヘルパーを利用して、
student_from_attribute
それを Lily の成績と比較することもできます。のように、これらすべての制約がより適切になり
student_from_attribute([A,B,C], country, italy, S), attrib_by_name(S, grade,
G), G1 < G
ます。これは、読書とサッカーの制約にも同じように適用できます。それは短くはありませんが、よりクリーンで一般的です。
誰もこれをすべて読むかどうかはわかりません:-)。とにかく、これらの考慮事項により、パズルは私にとって興味深いものになりました。