序言:谋杀之谜解决方案

Posted

技术标签:

【中文标题】序言:谋杀之谜解决方案【英文标题】:Prolog: Murder Mystery solution 【发布时间】:2020-08-29 17:29:06 【问题描述】:

我最近开始学习 Prolog 是为了好玩。我找到了以下murder mystery puzzle。由于除了基础知识之外我对 Prolog 知之甚少,因此我无法真正评估链接中提供的解决方案,但是,它对我来说似乎并不是特别好。我的解决方案不足以产生正确的答案,所以我正在寻找一些关于如何到达那里的指示,或者是否有可能用我的方法到达那里。这是一个谜题,以防万一链接断开:

要找出谁杀死了博迪先生,您需要了解每个人在哪里 是,房间里有什么武器。线索散落在各处 测验(在阅读所​​有 10 个问题之前,您无法解决问题 1)。

首先,您需要了解嫌疑人。三个男人(乔治, 约翰、罗伯特)和三个女人(芭芭拉、克里斯汀、约兰达)。每个 人在不同的房间(浴室、餐厅、厨房、客厅 房间,储藏室,书房)。在每个房间里都发现了一件可疑的武器(包, 火器、毒气、刀、毒药、绳子)。在厨房里发现了谁?

线索 1:没有发现厨房里的人拿着绳子、刀或 包。那么,哪件武器不是枪支,是在 厨房?

线索 2:芭芭拉要么在书房,要么在浴室;约兰达是 在另一个。芭芭拉是在哪个房间找到的?

线索 3:提包的人,不是芭芭拉也不是乔治,是 不在浴室,也不在餐厅。谁把包放在房间里 和他们一起?

线索4:在研究中发现了拿着绳子的女人。谁拥有 绳子?

线索 5:起居室里的武器是与 John 或 乔治。客厅里有什么武器?

线索 6:刀不在餐厅里。那么刀在哪里呢?

线索 7:Yolanda 没有携带研究中发现的武器,也没有携带 储藏室。在 Yolanda 身上发现了什么武器?

线索 8:枪支在乔治所在的房间里。在哪个房间里 找到枪支了吗?

人们发现博迪先生在储藏室里被毒死了。犯罪嫌疑人 在那个房间里发现的是凶手。那么,你指的是谁 手指向?

这是作者解决方案的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).

【问题讨论】:

您提到我提出的解决方案不容易阅读(我同意,实际上它应该只演示该方法)。您可以查看更新后的解决方案。 【参考方案1】:

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

关键是这个谓词对:

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 都是房子的成员,但与kitchen 在不同的房间。 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 .

【讨论】:

所以这是一个斑马拼图。我的答案中有一些类似的东西:***.com/…,***.com/search?q=user%3A849891+narrowing+domain。我打电话给你的members/2select/2,但条款的顺序相反。不知道哪个更好。还有mselect/3 可以让我们掌握剩余的域;有时也很有用。【参考方案2】:

编辑:在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

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

首先,您没有将对象之间的关系建模为数据。例如,对于“在研究中发现了拿着绳子的女人”。你有这个条款:

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

这确实有三个解决方案,可以将其解释为“关系killer(rope, study, barbara)killer(rope, study, christine)killer(rope, study, yolanda)”,但您的程序不知道如何解释它大大地。您实际上并没有构建表达这种关系的数据。这就是您链接到的解决方案正确执行的操作:它将房间和武器建模为可以绑定到代表人的原子的变量。因此它可以将这条线索表达为woman(Rope)(“拿着绳子的人是女人”)和Rope = Study(“绳子和书房是同一个人”)。

第二个大问题是您将所有线索建模为 same 谓词的不同子句。这是错误的,因为在 Prolog 中,谓词的不同子句表达了一个选择:如果第一个子句持有 or 第二个子句持有 or,则某事成立第三个子句成立,以此类推。但是您要表示第一个线索成立并且第二个线索成立并且第三个​​线索成立,等等。并且“and”被表达通过在 one 子句的主体中将不同的条件与, 结合起来。这就是为什么链接的解决方案有不同的谓词clue1clue2等,它们都是从一个大谓词的主体中调用的。

【讨论】:

我不同意:对房间和武器使用变量是非常糟糕的代码。然而,为每个线索设置一个谓词然后对每个线索进行连词的提示似乎很有希望。谓词应类似于clue1(Person, Weapon, Room) :- ...。然后通过限制每个变量来绑定它(person(Person), weapon(Weapon),room(Room),然后应用其他条件。 请解决这个问题并发布一个完整的解决方案,我很乐意学习。【参考方案3】:

从线索中依次推导出规则

每个人都在不同的房间(浴室、餐厅、厨房、 客厅、储藏室、书房)。每个房间都发现了可疑武器 (包、枪、毒气、刀、毒药、绳子)。

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).

现在让我们来看看

线索 1:没有发现厨房里的人拿着绳子、刀或 包。那么,哪件武器不是枪支,是在 厨房?

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

我们对 8 条线索中的每一条都执行此操作,最后

人们发现博迪先生在储藏室里被毒死了。犯罪嫌疑人 在那个房间里发现的是凶手。那么,你指的是谁 手指向?

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 和run。推理相当慢(但比作者的解决方案更快)。

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

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

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

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

为什么我认为规则集应该能够适应世界变化?

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

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

考虑一下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

总结

因此,如果您将 prolog 用于私人目的,我不介意“滥用”变量来快速解决问题。但是,如果您的规则集和数据增长,在我看来,显式地对所有关系建模是非常必要的。

【讨论】:

您可以在swish.swi-prolog.org 分享可运行 (SWI-)Prolog 代码,但在我看来 200 行不会太长,不能在这里发布。 您还没有解释为什么在原始解决方案中使用变量绑定是“误用”。我(仍然)碰巧不同意。逻辑变量具有独特的强大属性,为什么我们不应该使用它们?写出大量晦涩的变量名排列似乎更优雅、更强大或更易于维护。 我的描述更明确了。 谢谢。我认为类比到程序分析的论点是一个薄弱的论点:不同的实施策略适用于不同的应用领域。但更重要的是,您的可扩展性论点不成立。如果我们要将问题从五个武器/人员/房间扩展到六个,您将不得不重新审视所有手动扩展的组合,并进行大量无聊的复制和粘贴。 非常好的解决方案!这次真是万分感谢。我会注意到,如果将目标X = Gas, X = Pantry 移动到murderer/1 开头的适当位置,那么原版在速度上会轻松击败这个(因子 2 倍)。此外,用dif(X, Y) 替换所有\+ X = Y 目标并移动搜索(uniq_ppl 调用) 之后,线索将我机器上的速度从7 秒降低到0.003 秒。逻辑变量在 Prolog 中确实是一个强大的构造......在swish.swi-prolog.org/p/crime_constraints.pl 看到这个版本,我也会将它添加到我的答案中。

以上是关于序言:谋杀之谜解决方案的主要内容,如果未能解决你的问题,请参考以下文章

statement.executeQuery() 之谜。代码未执行,也未捕获异常。

如何在 Protégé 中用 OWL 解决爱因斯坦之谜时获得个人结果?

记录mybatis-plus多数据源批量操作时,数据源切换失效之谜

Prolog 中的“建筑”之谜

斑马之谜的年龄比较

Visual Studio 2013 Web 负载测试之谜