11.常用约束
现在我们来看clp(fd)库中提供的更方便的谓词。大多同时建立大量的简单约束。
除了引入了方便的谓词外,这也是用约束开发工具的好办法。
以下是在各种约束情况下有用的一些约束。
11.1.length(List, Length)
在length(-, +)模式下,生成未绑定变量列表很有用。
提示
length_(Length, List) :- length(List, Length).有时在调用+1情况下列出未绑定变量列表很方便。像length(List, 9), maplist(length_(9), List)给出了未绑定变量的二维数组。
length练习
?- length(X, 5)
现在尝试
?- length(X, Y).
11.2. all_different(List)
该谓词经常使用。它将列表的成员限制为不同的值。
all_different示例
?- length(List, 4),List ins 1..4, all_different(List), List = [1,_,3,4].
List = [1, 2, 3, 4].
all_different/1与鸽巢原理(抽屉原理)结合起来很有用,该原理指,如果你有N个变量,每个元素在1..M范围内,且N>M,那么一定有两个变量具有相同的值。出于这个原因,在上面的例子中,List肯定始终是[1,2,3,4]的排列。
all_distinct的工作方式类似于all_different,但是会尽力检测何时所有内容都不相同。
例如,我们尝试将3个变量约束在1..2范围中,且值不同——这是不可能的。
24 ?- X in 1..2, Y in 1..2, Z in 1..2, all_different([X,Y,Z]).
X in 1..2,
all_different([X, Y, Z]),
Y in 1..2,
Z in 1..2.
25 ?- X in 1..2, Y in 1..2, Z in 1..2, all_distinct([X,Y,Z]).
false.
26 ?-
上述内容中,all_different没有检测X、Y和Z只有两个可能的值,因此其中两个必须相同。
all_distinct会做更多工作来分析其变量的域,并会检测到这种情况。但是,使用all_distinct时cpu占用较高——因此,这需要提前裁剪搜索树以与性能提升进行平衡。
如果你遇到性能问题,那么使用其中一种方法进行审查可能是值得的。
11.3. tuples_in(+ListOfVariableTuples, +ListOfAcceptedTuples)
列出一些变量的可选组合列表是很常见的。一种常见的情况是点美式餐,你点的是一份套餐,套餐的价格包括一份主菜和两个配菜;但是如果你点的是馅饼,你只有一个配菜,等等。你需要列出所有不同的组合。
SWI-Prolog文档有个很酷的为旅行选择火车的演示。
示例5 火车旅程
:- use_module(library(clpfd)).
trains([[1,2,0,1], % 出发站,到达站,departs at, arrives at
[2,3,4,5],
[2,3,0,1],
[3,4,5,6],
[3,4,2,3],
[3,4,8,9]]).
threepath(A, D, Ps) :-
Ps = [[A,B,_T0,T1],[B,C,T2,T3],[C,D,T4,_T5]],
T2 #> T1,
T4 #> T3,
trains(Ts),
tuples_in(Ps, Ts).
?- threepath(1, 4, Ps).
Ps = [[1, 2, 0, 1], [2, 3, 4, 5], [3, 4, 8, 9]].
除了将它作为一种很酷的寻路方法之外,还请注意,我们无需做任何标记。这里只有一种解决方案。
如果发现元组看起来像[[1,2], [2,7], [3,8]…],请考虑使用element/3代替。
tuples_in练习
1.修改“火车旅程”程序,使它能查有着任意数量火车的路线,而不仅仅是3。
2.要求你提供一张有资格晋升的员工表。你有的此列表的列表,每个内部列表是单个员工的记录。这些字段有员工编号、上次考核分数、违反安全法规次数、任职时间、晋升所需时间
employees([
[1, 75, 0, 30, 1],
[2, 83, 0, 45, 1],
[3, 90, 1, 45, 2],
[4, 45, 3, 75, 1],
[5, 89, 0, 52, 2]
]).
time_in_grade([[1,25], [2,50]]).
员工的最后考核成绩必须在80分以上,不超过1次安全违规,任职时间需超过晋升要求的时间,方有资格晋升。
3.最后一列,提升所需的时间,没有标准化。员工要么是团队成员,要么是团队领导。团队成员需要25周的任职时间,团队领导需要50周。首席架构师决定将其抽取到第二张表中,以便可以轻松更改这些数字。
employees([
[1, 75, 0, 30, 1],
[2, 83, 0, 45, 1],
[3, 90, 1, 45, 2],
[4, 45, 3, 75, 1],
[5, 89, 0, 52, 2]
]).
time_in_grade([[1,25], [2,50]]).
更新练习2,使用新的数据格式。
11.4.数独
好吧,这实际不是真正的数独谓词,但当变量被自然地表示为一个2D网格时,transpose(+Matrix, ?Transpose) 很有用。
将网格表示为列表的列表。每个列表就是一行。
许多约束谓词都可以处理列表中元素的相邻关系。如果您需要对行进行操作,则可以用maplist。要对列进行操作,请转置矩阵,它们现在就是行了。
提示
不,你不需要将其转置回来。新的转置矩阵与原先的共享。对它的任何约束都是对原先的约束。
例如,这是个解决“孩子吵架”问题的程序。通过只处理行,它就大大简化了
孩子吵架
/*
孩子吵架概述
16个孩子要坐在一个4 x 4椅子。
孩子有8个女孩(编号1..8),8个男孩(编号9..16)。
1,3,5,8认为男孩很烂
9,10,11,14认为女孩恶心
这几对是冤家
[[1,2], [4,6], [4,7], [4, 9],[9,11], [12, 14], [14,16]]
*/
length_(Length, List) :- length(List, Length).
child_row(X) :- X ins 1..16 .
ww(X) :-
write(X),
write('/').
print_row(Row) :-
maplist(ww, Row),
nl.
children(Class) :-
length(Class, 4),
maplist(length_(4), Class),
maplist(child_row , Class),
maplist(row_compatible, Class),
transpose(Class, TransClass),
maplist(row_compatible, TransClass),
flatten(Class, FlatClass),
all_different(FlatClass),
maplist(label, Class),
maplist(print_row, Class).
row_compatible([A,B,C,D]) :-
compatible(A, B),
compatible(B, C),
compatible(C, D).
compatible(A, B) :-
not_enemy(A, B),
not_enemy(B, A),
sex_compatible(A, B),
sex_compatible(B, A).
not_enemy(A, B) :-
NotA #\= A #\/ NotB #\= B,
tuples_in([[NotA, NotB]],
[[1,2], [4,6], [4,7], [4, 9],[9,11], [12, 14], [14,16]]).
sex_compatible(A, B) :-
A in 1\/3\/5\/8 #==> B #=< 8,
A in 9..11\/14 #==> B #> 8.
transpose练习
编写成为roguelike游戏的一部分的谓词。
您的游戏板是二维数组(列表的列表)。 每格包含以下元素之一
0-地面
1-墙
2-怪物
3-玩家
写一个谓词,can_move(+Board, -Moves)。Board是如上定义的游戏板。SafeMoves是个列表的列表,玩家可以移动的地方为1,不能移动的地方为0。必须遵守以下规则:
玩家可以向水平、垂直但不是对角线方向移动0、1、2或3个空格。
玩家无法穿过或到达墙上
玩家不能穿过或与怪物相邻
玩家无法移开游戏盘
示例运行(出于可读性采取了一些措施):
游戏板示例
board([
[1,1,1,1,1,1,1,1],
[1,0,2,0,0,0,0,0],
[0,1,0,0,0,2,0,0],
[0,0,1,0,0,0,0,3],
[0,0,0,1,0,0,2,0],
[0,0,0,0,1,0,0,0],
[0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,1]
]).
?- board(B), can_move(B, M), writeq(M).
M = [
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,1,1],
[0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]
]
11.5. zcompare(?Order, ?A, ?B)
当你需要了解两个未标记变量的域之间的关系时,zcompare很有用。
10 ?- X in 0..10, Y in 11..20,zcompare(C, X, Y).
C = (<),
X in 0..10,
Y in 11..20.
11 ?- X in 0..11, Y in 11..20,zcompare(C, X, Y).
X in 0..11,
zcompare(C, X, Y),
freeze(C, clpfd:zcompare_(C, X, Y)),
Y in 11..20.
245/5000
我承认,第二个让我有些困惑。事实证明,C仍然不受约束,因为当它们的域重叠时,我们无法真正分辨X和Y之间的关系。但是,如果C稍后绑定,它将返回并约束X和Y!
15 ?- X in 0..11, Y in 11..20,zcompare(C, X, Y),C = <, writeln(C).
<
C = (<),
X in 0..11,
X#=<Y+ -1,
zcompare(<, X, Y),
Y in 11..20.
提示
试着想像调试代码时使用了大量zcompare
zcompare练习
创建一个约束谓词same_side_of_line/4,其参数是词典形式
point{x:12.34, y:56.78}
前两个参数是直线上的点,后两个参数是两个要测试点。 如果两个测试点都在直线的同侧,或两个都在直线上,则谓词成功,约束成立