我刚刚在math.SE(source)上看到了这个问题:
相同尺寸的六本不同书籍(A,B,C,D,E,F)如下所示 图:
我们知道以下事实:
- A和D没有接触。
- E介于两本垂直或水平的书之间。
- C恰好涉及两本书。
- A和F touch。
- E和F沿着他们的封面(长边)触摸
醇>有多少本书有他们的职位?
我以为我可以用Prolog解决这个问题:
% Those are the books:
book(a).
book(b).
book(c).
book(d).
book(e).
book(f).
% This is how 'touching' works:
touching(X,Y):- touching(Y,X). % touching is symmetric
touching(p1,p2).
touching(p2,p3).
touching(p3,p4).
touching(p3,p5).
touching(p3,p6).
touching(p4,p5).
touching(p5,p6).
% List all possible positions:
position(a):- p1,p2,p3,p4,p5,p6.
position(b):- p1,p2,p3,p4,p5,p6.
position(c):- p1,p2,p3,p4,p5,p6.
position(d):- p1,p2,p3,p4,p5,p6.
position(e):- p1,p2,p3,p4,p5,p6.
position(f):- p1,p2,p3,p4,p5,p6.
% Every position has one book
getBook(p1) :- a,b,c,d,e,f.
getBook(p2) :- a,b,c,d,e,f.
getBook(p3) :- a,b,c,d,e,f.
getBook(p4) :- a,b,c,d,e,f.
getBook(p5) :- a,b,c,d,e,f.
getBook(p6) :- a,b,c,d,e,f.
% Add your facts:
not(touching(position(a),position(d))).
position(e):- p5,p2.
% C touches exactly two books: eventually something like aggregate_all(count, touching(e,X), Count):-2.
position(c):- p2, p4,p6.
touching(position(a),position(f)).
touching(position(e),position(f)).
但是当我尝试position(a)
时,我得到了:
?- consult(books).
Warning: /home/moose/Downloads/LaTeX-examples/documents/Programmierparadigmen/scripts/prolog/books.pl:37:
Clauses of position/1 are not together in the source-file
Warning: /home/moose/Downloads/LaTeX-examples/documents/Programmierparadigmen/scripts/prolog/books.pl:40:
Clauses of touching/2 are not together in the source-file
% books compiled 0.00 sec, 32 clauses
true.
?- position(a).
ERROR: position/1: Undefined procedure: p1/0
Exception: (7) p1 ?
答案 0 :(得分:3)
这就是症结所在。您需要找到[1,2,3,4,5,6]
的排列,使用符合某些约束的逻辑变量标记为[A,B,C,D,E,F]
。约束是书籍触摸和书籍水平或垂直对齐。我们掌握的事实是
vert(1).
vert(2).
vert(3).
horz(4).
horz(5).
horz(6).
以及书籍之间的一些关系,即
touch(3, 4).
touch(3, 5).
touch(3, 6).
touch_long(1, 2).
touch_long(2, 3).
touch_long(4, 5).
touch_long(5, 6).
touching(X, Y) :-
touch(X, Y) ; touch(Y, X); touching_long(X, Y).
touching_long(X, Y) :-
touch_long(X, Y) ; touch_long(Y, X).
蛮力方式(足以解决这么小的问题)就是生成排列并检查约束。这在Prolog编程中称为生成和测试方法。
% books(A, B, C, D, E, F) unifies its variables with the
% integers 1 through 6 to meet the constraints.
books(A, B, C, D, E, F) :-
permutation([1, 2, 3, 4, 5, 6], [A, B, C, D, E, F]),
% 1. A and D are not touching.
\+ touching(A, D),
% 2. E is between two books which are both vertical or both horizontal.
% We can take a shortcut by exploiting the asymmetry in touch_long.
touch_long(_, E),
touch_long(E, _),
% 3. C touches exactly two books. That means that the set of all books
% touching C has cardinality 2.
setof(X, touching(X, C), TouchingC),
length(TouchingC, 2),
% 4. A and F touch.
touching(A, F),
% 5. E and F touch along their cover (long side).
touching_long(E, F).
您现在可以运行books(A,B,C,D,E,F)
来生成有效的排列:
?- books(A,B,C,D,E,F).
A = 3,
B = 2,
C = 4,
D = 1,
E = 5,
F = 6 ;
A = 3,
B = 2,
C = 6,
D = 1,
E = 5,
F = 4
等。通过观察输出可以解决原始问题;为原始程序编写一个全自动的解决方案留作练习(这有点单调乏味)。
答案 1 :(得分:2)
编辑:修正了一个错误(关于“E和F触摸其封面(长边)”的规则“使用D代替F)。
使用ECLiPSe CLP的约束编程解决方案Prolog:
:- lib(gfd).
model(Books) :-
[A, B, C, D, E, F] = Books,
Books :: 1..6,
alldifferent(Books),
% A and D are not touching.
abs(A - D) #\= 1, (A #= 3) => (D #< 4), (D #= 3) => (A #< 4),
% E is between two books which are both vertical or both horizontal.
E :: [2, 5],
% C touches exactly two books.
C :: [2, 4, 6],
% A and F touch.
(abs(A - F) #= 1) or (A #= 3 and F #> 4) or (F #= 3 and A #> 4),
% E and F touch along their cover (long side)
abs(E - F) #= 1, (E #> 3) => (F #> 3), (E #< 4) => (F #< 4).
find(Books) :-
findall(Books, (model(Books), labeling(Books)), Sols),
table(Books, Sols).
执行命令
[eclipse]: find([A, B, C, D, E, F]).
A = A{[3 .. 6]}
B = B{[2, 4 .. 6]}
C = C{[2, 4, 6]}
D = 1
E = E{[2, 5]}
F = F{[3, 4, 6]}
所以,只有书D知道位置-1。