这是我的看法。它本质上是 @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 的解决方案形成了很好的对比)。
- 正如其他人指出的那样,一方面是如何编码信息
问题描述中给出。你可以很具体的表达出来(解决
只是这种情况),或者更笼统(例如允许将问题扩展到
3 名以上学生)。
- 这个问题与其他类似问题的不同之处在于,你有一个混合
影响单个学生的约束(“杰克来自
德国”),影响两名学生(“莉莉的成绩比来自
意大利”),或涉及所有人(“喜欢足球的人最好
成绩”)。
- 此外,您还有析取约束(“它们是 all
国家,并且有不同的爱好”)。Prolog非常擅长通过
一个事实的所有可能实例,但拥有它更复杂
选择一个实例并将这个实例留给谓词的下一次调用。
这迫使您找到一种方法来从一个事实中获取一组值
成对不同。 (例如,当 Prolog 满足于 Lily 的阅读爱好时,
它不能也将阅读作为杰克的爱好)。
- 所以在列出所有已知事实及其可能值 (1) 之后,我首先
定义了一个谓词
student/4 (2) 来简单地说明一个学生有这些
4个属性。这产生了学生和他们的所有可能的组合
属性,也允许它们都具有相同的名称,来自
同一个国家,asf。
- 如何在 Prolog 中创建结果集也是一个很好的例子
大,然后尝试进一步缩小范围(就像其他人写的那样)。
进一步的谓词可以利用这个“生成器”并越来越多地过滤
其结果集中的解决方案。这也更容易测试,在每个阶段你
可以检查中间输出是否有意义。
- 在下一个谓词
students/3 (3) 中,我尝试了我提到的内容
早些时候,创建至少不使用相同的学生实例
属性两次(就像两个有相同爱好的学生)。为了实现这一点,我
必须遍历我所有的属性事实(名称/1,国家/1,...),得到
每个三个值,并确保它们成对不同。
- 不必为每个属性显式执行此操作
除了名称之外,实现将始终相同
属性,我构造了一个辅助谓词
permute/4 (4),我可以通过
属性名称,它将将该属性作为事实查找三遍,
并确保绑定的值都不相同。
- 所以当我在
students/3 (5) 中调用permute(name,N1,N2,N3) 时,它会导致
查找call(P,X), call(P,Y), call(P,Z) (6) 结果与
调用name(X), name(Y), name(Z)。 (因为我正在收集 3 个不同的值
从始终具有相同属性的 3 个事实来看,这实际上与执行 3 排列相同
一个 3 值集,因此得名辅助谓词。)
- 当我到达 (7) 时,我知道每个学生属性都有不同的值,
我只是将它们分布在三个学生实例中。 (这应该
在没有
student/4 谓词的情况下实际上可以正常工作
在 Prolog 中即时组成这样的结构化术语。拥有student
谓词提供了额外的好处,即检查没有愚蠢的学生
可以构造,例如“student(lily, 23, asdf, -7.4)”。)
- 所以
:- students(A,B,C). 产生了 3 个学生的所有可能组合,并且
他们的属性,没有使用任何涉及的属性两次。好的。
它还方便地包装了(更难的)student() 结构
使界面更加简洁的单字母变量。
- 但除了那些不相交的约束,我们还没有实施任何
另一个约束。这些现在遵循(不太优雅)
who/3
谓词 (8)。它基本上使用students/3 作为生成器并尝试
通过添加更多约束过滤掉所有不需要的解决方案(因此它有
与students/3基本相同的签名。)
- 现在另一个有趣的部分开始了,因为我们不仅要能够过滤
单独的
student 实例,也可以单独引用它们
(“Daisy”,“Jack”,...)及其各自的属性(“Daisy's hobby”
等等。)。因此,在绑定我的结果变量 A、B 和 C 时,我进行模式匹配
特定的名称。因此,来自 (9) 的文字名称 lily、jack asf。这
让我不必考虑莉莉可能先出现的情况,或者作为
第二,或第三(因为students/3 会产生这样的排列)。所以
所有 3 组不按此顺序来的学生都将被丢弃。
- 我也可以稍后在像
N1 =
lily asf 这样的显式约束中这样做。我现在这样做是为了执行杰克来自德国的简单事实
黛西喜欢烹饪 (10)。当这些失败时,Prolog 会回溯到
首次致电students/3,以获取可以尝试的另一组学生。
- 现在关注关于 Lily 成绩、Jack 成绩和
足球爱好者的成绩 (11)。这是特别难看的代码。
- 首先,最好有能够返回的辅助谓词
查询“具有属性 X 的学生”的答案。这将采取
当前选择的学生 A、B 和 C、属性名称(“国家”)和
value ('italy') 并返回相应的学生。所以你可以
查询来自意大利的学生,而不是假设它必须是第二个
或者第三个学生(正如问题描述所暗示的那样,莉莉本人
不是来自意大利)。
- 所以这个假设的辅助谓词,我们称之为
student_from_attribute
将需要另一个帮助器来按名称在学生结构中查找值
并返回相应的值。在所有支持某些语言的语言中都很容易
一种对象/命名元组/记录,您可以通过以下方式访问其中的字段
姓名。但香草 Prolog 没有。所以会有一些提升要做,
一些我无法摆脱的东西。
-
who/3 谓词也可以利用这个其他助手,因为您
需要与返回的学生不同的属性
student_from_attribute,例如“等级”,并将其与莉莉的等级进行比较。
这将使所有这些约束变得更好,比如
student_from_attribute([A,B,C], country, italy, S), attrib_by_name(S, grade,
G), G1 < G。这可以以相同的方式应用于阅读和足球
约束。这不会更短,而是更简洁、更通用。
我不确定是否有人会阅读所有这些内容:-)。不管怎样,这些考虑让我觉得这个谜题很有趣。