Prolog:谋杀之谜解决方案

Prolog: Murder Mystery solution

我最近开始学习 Prolog 是为了好玩。我找到了以下 murder mystery puzzle。由于我对 Prolog 除了基础知识了解不多,所以我无法真正评估 link 中提供的解决方案,但是,它对我来说似乎并不是特别好。我的解决方案不足以生成正确的答案,因此我正在寻找一些关于如何到达那里或是否有可能通过我的方法到达那里的指示。以防万一 link 下降,这里是拼图:

To discover who killed Mr. Boddy, you need to learn where each person was, and what weapon was in the room. Clues are scattered throughout the quiz (you cannot solve question 1 until all 10 are read).

To begin, you need to know the suspects. There are three men (George, John, Robert) and three women (Barbara, Christine, Yolanda). Each person was in a different room (Bathroom, Dining Room, Kitchen, Living Room, Pantry, Study). A suspected weapon was found in each room (Bag, Firearm, Gas, Knife, Poison, Rope). Who was found in the kitchen?

Clue 1: The man in the kitchen was not found with the rope, knife, or bag. Which weapon, then, which was not the firearm, was found in the kitchen?

Clue 2: Barbara was either in the study or the bathroom; Yolanda was in the other. Which room was Barbara found in?

Clue 3: The person with the bag, who was not Barbara nor George, was not in the bathroom nor the dining room. Who had the bag in the room with them?

Clue 4: The woman with the rope was found in the study. Who had the rope?

Clue 5: The weapon in the living room was found with either John or George. What weapon was in the living room?

Clue 6: The knife was not in the dining room. So where was the knife?

Clue 7: Yolanda was not with the weapon found in the study nor the pantry. What weapon was found with Yolanda?

Clue 8: The firearm was in the room with George. In which room was the firearm found?

It was discovered that Mr. Boddy was gassed in the pantry. The suspect found in that room was the murderer. Who, then, do you point the finger towards?

这里是link作者的解决方案。

这是我尝试的解决方案:

male(george).
male(john).
male(robert).

female(barbara).
female(christine).
female(yolanda).

person(X) :- male(X).
person(X) :- female(X).

room(kitchen).
room(bathroom).
room(diningroom).
room(livingroom).
room(pantry).
room(study).

weapon(bag).
weapon(firearm).
weapon(gas).
weapon(knife).
weapon(poison).
weapon(rope).

/*
Clue 1: The man in the kitchen was not found with 
        the rope, knife, or bag. 
        Which weapon, then, which was not the firearm, 
        was found in the kitchen?
*/

/* X is Weapon, Y is Room, Z is Person */

killer(X, Y, Z) :-
    room(Y) = room(kitchen),
    male(Z),
    dif(weapon(X), weapon(rope)),
    dif(weapon(X), weapon(knife)),
    dif(weapon(X), weapon(bag)),
    dif(weapon(X), weapon(firearm)).

/*
Clue 2: Barbara was either in the study or the bathroom; 
        Yolanda was in the other. 
        Which room was Barbara found in?
*/

/* It was easy to deduce the following from other data */

killer(X, Y, Z) :-
    female(Z) = female(barbara),
    room(study) = room(Y).

killer(X, Y, Z) :-
    female(Z) = female(yolanda),
    room(bathroom) = room(Y).

/*
Clue 3: The person with the bag, who was not Barbara nor 
        George, was not in the bathroom nor the dining room. 
        Who had the bag in the room with them?
*/
killer(X, Y, Z) :-
    weapon(bag) = weapon(X),
    dif(room(Y), room(bathroom)),
    dif(room(Y), room(diningroom)),
    dif(person(Z), male(george)),
    dif(person(Z), female(barbara)).


/*
Clue 4: The woman with the rope was found in the study. 
        Who had the rope?
*/
killer(X, Y, Z) :- 
    weapon(rope) = weapon(X),
    room(study) = room(Y),
    female(Z).

/*
Clue 5: The weapon in the living room was found with either 
        John or George. What weapon was in the living room?
*/

killer(X, Y, Z) :-
    room(Y) = room(livingroom),
    dif(male(Z), male(robert)).

/*
Clue 6: The knife was not in the dining room. 
        So where was the knife?
*/

killer(X, Y, Z) :-
    weapon(knife) = weapon(X),
    room(Y) \= room(diningroom).

/*
Clue 7: Yolanda was not with the weapon found 
        in the study nor the pantry. 
        What weapon was found with Yolanda?
*/

killer(X, Y, Z) :-
    female(yolanda) = female(Z),
    dif(room(study), room(Y)),
    dif(room(pantry), room(Y)).

/*
Clue 8: The firearm was in the room with George. 
        In which room was the firearm found?
*/

killer(X, Y, Z) :-
    weapon(firearm) = weapon(X),
    male(george) = male(Z).

/*
It was discovered that Mr. Boddy was gassed in the pantry. 
The suspect found in that room was the murderer. 
Who, then, do you point the finger towards?
*/

killer(X, Y, Z) :-
    room(Y) = room(pantry),
    weapon(X) = weapon(gas).

编辑:https://swish.swi-prolog.org/p/crime_constraints.pl 查看参考解决方案的改进版本。

我同意您链接到的解决方案很丑陋,但它确实使用了正确的方法。你的方向不太对。一些评论:

/* X is Weapon, Y is Room, Z is Person */

那为什么不用变量名WeaponRoomPerson呢?它使您的程序更易于阅读。

weapon(rope) = weapon(X)

这完全等同于只写 X = roperope = X

但是除了这些之外,您解决这个难题的方式还有另外两个大问题:

首先,您没有将对象之间的关系建模为数据。例如,对于 "The woman with the rope was found in the study." 你有这个子句:

killer(X, Y, Z) :- 
    weapon(rope) = weapon(X),
    room(study) = room(Y),
    female(Z).

这确实有三个解决方案可以解释为"a relation killer(rope, study, barbara), killer(rope, study, christine), or killer(rope, study, yolanda)",但你的程序不知道如何那样解释它。您实际上并没有构建表达这种关系的数据。这就是您链接到的解决方案正确执行的操作:它将房间和武器建模为可以绑定到代表人的原子的变量。因此可以将这条线索表示为woman(Rope)("the person with the Rope is a woman")和Rope = Study("the rope and the study are associated with the same person")。

第二个大问题是您将所有线索建模为 相同 谓词的不同子句。这是错误的,因为在 Prolog 中,谓词的不同子句表达了一个 选择:如果第一个子句成立 ,则第二个子句成立 or 第三个子句成立,等等。但是你想表达第一个线索成立 and 第二个线索成立 and第三条线索成立等。而"and"是通过在one子句的主体中用,组合不同的条件来表达的。这就是为什么链接的解决方案有不同的谓词 clue1clue2 等,所有这些都是从一个大谓词的主体中调用的。

按顺序从线索中推导出规则

Each person was in a different room (Bathroom, Dining Room, Kitchen, Living Room, Pantry, Study). A suspected weapon was found in each room (Bag, Firearm, Gas, Knife, Poison, Rope).

unique(A,B,C,D,E,F) :- 
     A \= B, A \= C, A \= D, A \= E, A \= F,
     B \= C, B \= D, B \= E, B \= F,
     C \= D, C \= E, C \= F,
     D \= E, D \= F,
     E \= F.

suspicious(pwr(george,WA,RA), pwr(john,WB,RB), pwr(robert,WC,RC), pwr(barbara,WD,RD), pwr(christine,WE,RE), pwr(yolanda,WF,RF)) :- 
    weapon(WA), weapon(WB), weapon(WC), weapon(WD), weapon(WE), weapon(WF),
    unique(WA,WB,WC,WD,WE,WF),
    room(RA), room(RB), room(RC), room(RD), room(RE), room(RF),
    unique(RA,RB,RC,RD,RE,RF).

现在让我们检查一下

Clue 1: The man in the kitchen was not found with the rope, knife, or bag. Which weapon, then, which was not the firearm, was found in the kitchen?

clue1(L) :-
    oneof(pwr(P,W,kitchen),L),
    male(P),
    weapon(W),
    W \= rope, W \= knife, W \= bag, W \= firearm.

我们对 8 条线索中的每一条都这样做,最后

It was discovered that Mr. Boddy was gassed in the pantry. The suspect found in that room was the murderer. Who, then, do you point the finger towards?

killer(X, L) :- member(pwr(X,gas,pantry),L).

resolved(X) :-
    suspicious(A,B,C,D,E,F),
    L = [A,B,C,D,E,F],
    clue1(L),
    clue2(L),
    clue3(L),
    clue4(L),
    clue5(L),
    clue6(L),
    clue7(L),
    clue8(L),
    killer(X, L).

完整的程序可以是 found and run。推理速度相当慢(但比作者的解决方案快)。

为什么认为使用关系而不是变量绑定是更好的设计?

我将序言程序理解为获取知识的规则集。这意味着:

  • prolog 中的每个关系都应该描述域中的一个关系
  • 向世界添加实体(武器、人物、房间)不应使规则集过时。问题没有改变(我们只是扩展了世界)所以规则和查询不需要触及。
  • 扩展问题(例如通过添加第七个位置)应该影响最小

引用的解决方案中并非每个方面都是最优的,如果对 prolog 更熟悉一些可能会更好地表达。

为什么我认为规则集应该对世界变化具有鲁棒性?

我在程序分析中使用了datalog。这意味着源代码(或字节代码)中的每个关系都被建模为事实和规则推断类型、安全漏洞、设计模式等。有数百万个事实和数千个规则集代码。添加实体(例如源代码行、类型注释)不应该促使我重新实现规则集代码(很难正确编写)。

为什么我认为使用隐式关系是糟糕的代码?

考虑来自 reference solution 的这段代码,它完全是误导:

clue1(Bathroom, Dining, Kitchen, Livingroom, Pantry, Study, Bag, Firearm, Gas, Knife, Poison, Rope) :-
   man(Kitchen),       // a man is a kitchen?
   \+Kitchen=Rope,     // a kitchen is not a rope?
   \+Kitchen=Knife,    // a kitchen is not a knife?
   \+Kitchen=Bag,      // a kitchen is not a bag
   \+Kitchen=Firearm.  // a kitchen is not a firearm

好吧,变量名很难看,可读性更好

clue1(InBathroom, InDiningroom, InKitchen, InLivingroom, InPantry, InStudy, WithBag, WithFirearm, WithGas, WithKnife, WithPoison, WithRope) :-
   man(InKitchen),     // (person) in the kitchen is a man - ok
   \+Kitchen=Rope,     // (person) in the kitchen is not 
                          (person) with a rope - better than above
   \+Kitchen=Knife,    // ...
   \+Kitchen=Bag,      // ...
   \+Kitchen=Firearm.  // ...

但是我们误用了明确的相等关系。有一个明确的指标:名称中包含谓词的变量很可能是隐式关系。 "personInKitchen" 是一个(逻辑)谓词 "in" 连接两个实体 "person" 和 "kitchen"。

作为模型与列表和函数符号的比较(suspect/3 是将人与武器和房间联系起来的关系函数,Suspects 是嫌疑人列表):

clue1(Suspects) :-    
    member(suspect(Person,Weapon,Room),Suspects), 
    male(Person),     // The man (Person)
    Room = kitchen,   // in the Kitchen (Room)
    Weapon \= rope,   // was not found with the (Weapon) rope
    Weapon \= knife,  // (Weapon) knife
    Weapon \= bag,    // (Weapon) bag
    Weapon \= firearm.// (Weapon) firearm

总结

所以如果你将序言用于私人目的,我不介意"misusing" 变量来快速解决。但是,如果您的规则集和数据增长,在我看来明确地对所有关系建模是非常必要的。

我对这个问题采取了更积极的态度。我没有尝试任何形式的否定,而是选择了简单的统一。

键是这个谓词对:

members([],_).
members([M|Ms],Xs) :- select(M,Xs,Ys),members(Ms,Ys).

这是一个基本的排列谓词。它将获取第一个参数的列表,并尝试针对第二个列表的所有排列进行统一。

现在很多规则变得很容易表达了:

例如线索1:

clue1(House) :- members([[P,kitchen,_],[_,_,rope],[_,_,knife],[_,_,bag],[_,_,firearm]],House),man(P).

所以这意味着 ropeknifebagfirearm 都是房子的成员,但与 [=18= 在不同的房间]. Prolog 将继续回溯,直到它发现适合这些项目。

这是我的完整解决方案:

man(george).
man(john).
man(robert).
woman(barbara).
woman(christine).
woman(yolanda).

members([],_).
members([M|Ms],Xs) :- select(M,Xs,Ys),members(Ms,Ys).

clue1(House) :- members([[P,kitchen,_],[_,_,rope],[_,_,knife],[_,_,bag],[_,_,firearm]],House),man(P).
clue2(House) :- member([barbara,study,_],House), member([yolanda,bathroom,_],House).
clue2(House) :- member([barbara,bathroom,_],House), member([yolanda,study,_],House).
clue3(House) :- members([[_,_,bag],[barbara,_,_],[george,_,_]],House),members([[_,_,bag],[_,bathroom,_],[_,dining_room,_]],House).
clue4(House) :- members([[P,study,rope]],House),woman(P).
clue5(House) :- members([[john,living_room,_]],House).
clue5(House) :- members([[george,living_room,_]],House).
clue6(House) :- members([[_,_,knife],[_,dining_room,_]],House).
clue7(House) :- members([[yolanda,_,_],[_,study,_],[_,pantry,_]],House).
clue8(House) :- member([george,_,firearm],House).
clue9(House,P) :- members([[P,pantry,gas]],House).

solve(X) :-
    House = [[_,bathroom,_],[_,dining_room,_],[_,kitchen,_],[_,living_room,_],[_,pantry,_],[_,study,_]],
    clue1(House),
    clue2(House),
    clue3(House),
    clue4(House),
    clue5(House),
    clue6(House),
    clue7(House),
    clue8(House),
    clue9(House,X),
    members([[george,_,_],[john,_,_],[robert,_,_],[barbara,_,_],[christine,_,_],[yolanda,_,_]],House),
    members([[_,_,bag],[_,_,firearm],[_,_,gas],[_,_,knife],[_,_,poison],[_,_,rope]],House),
    write(House),
    true.

这给了我:

?- solve(X).
[[yolanda,bathroom,knife],[george,dining_room,firearm],[robert,kitchen,poison],[john,living_room,bag],[christine,pantry,gas],[barbara,study,rope]]
X = christine .