如何用Prolog解决'6本书'之谜?

时间:2014-03-05 13:24:47

标签: prolog

我刚刚在math.SE(source)上看到了这个问题:

  

相同尺寸的六本不同书籍(A,B,C,D,E,F)如下所示   图:

     

enter image description here

     

我们知道以下事实:

     
      
  1. A和D没有接触。
  2.   
  3. E介于两本垂直或水平的书之间。
  4.   
  5. C恰好涉及两本书。
  6.   
  7. A和F touch。
  8.   
  9. E和F沿着他们的封面(长边)触摸
  10.         

    有多少本书有他们的职位?

我以为我可以用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 ? 
  • 问题1:为什么我会遇到异常以及如何解决?
  • 问题2:有没有办法在prolog中描述更接近文本的事实3?
  • 问题3:如何打印所有组合?

2 个答案:

答案 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。