我想用Prolog解决这个逻辑难题,而不使用任何内置函数或库。问题是我不知道如何在代码中表达负项。否定词出现在第二、第三和第七部分。
例如,如果我想在第二句中否定东非:adventure(B, T), hunter(B, doctor), not(place(B, east_africa))
,求解器将不知道在这些地方之间有这个选项。
逻辑拼图:
五个人坐在狩猎俱乐部的桌旁,谈论他们最近的不可思议的经历。他们都在奇怪的环境中,一次搏斗一只动物。根据所提供的信息,确定这些冒险发生在哪里,他们是什么样的动物,以及他们用什么工具挣扎。- 教授突然抓起一块大石头把动物扔了出去。
- 医生没有在东非打猎,也没有被河马袭击。
- 上校的犀牛冒险并不是在中非,在那里一个猎人徒手赶走了一头动物。 北美野牛袭击了一名猎人。
- 消防队长在南非打猎。
- 美洲狮被船长用空枪击中头部。
- 西非的猎人没有枪,他也不是那个穿着衣服和攻击者搏斗的人。
- 大象不是被棍子赶走的。
我写的代码
solve(T) :-
adventure(A, T), hunter(A, professor), tool(A, stone),
adventure(B, T), hunter(B, doctor),
adventure(C, T), hunter(C, colonel), animal(C, rhino),
adventure(D, T), place(D, central_africa), tool(D, bare_hands),
adventure(E, T), place(E, north_africa), animal(E, bison),
adventure(F, T), hunter(F, fire_chief), place(F, south_africa),
adventure(G, T), hunter(G, captain), animal(G, puma), tool(G, empty_gun),
adventure(H, T), place(H, west_africa),
adventure(I, T), animal(I, elephant).
adventure(X, adventures(X,_,_,_,_)).
adventure(X, adventures(_,X,_,_,_)).
adventure(X, adventures(_,_,X,_,_)).
adventure(X, adventures(_,_,_,X,_)).
adventure(X, adventures(_,_,_,_,X)).
hunter(a(X,_,_,_),X).
place(a(_,X,_,_),X).
animal(a(_,_,X,_),X).
tool(a(_,_,_,X),X).
这是一种解决谜题的方法,无需Prolog内置和使用否定,通过将提供的所有信息以相同的形式"否定";的形式。这可能不是最理想的解决方案,但我认为它可以展示从不同的角度思考事实。
首先,让我们对数据进行编码,并进行"冒险"。是:
hunter(professor).
hunter(doctor).
hunter(colonel).
hunter(fire_chief).
hunter(captain).
tool(stone).
tool(hands).
tool(garment).
tool(gun).
tool(stick).
place(east_africa).
place(central_africa).
place(north_africa).
place(south_africa).
place(west_africa).
animal(rhino).
animal(hippo).
animal(bison).
animal(puma).
animal(elephant).
adventure([H, T, P, A]) :- hunter(H), tool(T), place(P), animal(A).
这为思考问题和"冒险"提供了一个很好的起点。需要四种不同项目中的一种,我选择将其包装在一个列表中,以便稍后进行比较。
使用上述规则,我们可以很容易地生成adventure(A)
的解。为了使解符合这个谜题,让我们添加一个规则invalid/1
,它采用冒险列表,这样我们就可以查询adventure(A), + invalid(A)
并知道"A
是一个不无效的冒险">
% already invalid conditions from the puzzle
invalid([doctor, _, east_africa, _]).
invalid([doctor, _, _, hippo]).
invalid([colonel, _, central_africa, _]).
invalid([_, gun, west_africa, _]).
invalid([_, garment, west_africa, _]).
invalid([_, stick, _, elephant]).
% valid conditions from the puzzle negated
invalid([H, T, _, _]) :-
+ ((H = professor, T = stone) ; (H = professor, T = stone)).
invalid([H, _, _, A]) :-
+ ((H = colonel, A = rhino) ; (H = colonel, A = rhino)).
invalid([_, T, P, _]) :-
+ ((T = hands, P = central_africa) ; (T = hands, P = central_africa)).
invalid([_, _, P, A]) :-
+ ((P = north_africa, A = bison) ; (P = north_africa, A = bison)).
invalid([H, _, P, _]) :-
+ ((H = fire_chief, P = south_africa) ; (H = fire_chief, P = south_africa)).
invalid([H, T, _, A]) :-
+ ((H = captain, T = gun, A = puma) ; (H = captain, T = gun, A = puma)).
这种方法让我们很容易写出"否定的"。,生成的冒险列表不能与invalid
匹配("医生不在东非";"医生没有遇到河马";等)
对于"正的";在谜题中的信息,这需要在逻辑上否定它,留下一些混乱的情况。自invalid
谓词中的一个需要被证明是假的,冒险列表需要准确地包含谜题中的内容,或者什么都不包含("教授使用了石头,或者两者都没有提到";"上校遇到了犀牛,或者两者都没有提到"),这就是为什么逻辑否定是类似于x的("如果教授使用了石头以外的东西,或者另一个猎人使用了石头")。
使用它可以生成符合谜题的冒险。接下来,让我们用剩下的唯一性约束生成一个答案列表:
solve(A) :- solve(A, []).
solve(A, A) :-
A = [[professor|_], [doctor|_], [colonel|_], [fire_chief|_], [captain|_]].
solve(A, Adventures) :-
make_adventure(NewA, Adventures),
solve(A, [NewA|Adventures]).
make_adventure(A, Adventures) :-
adventure(A), + invalid(A),
unique_adventure(A, Adventures).
unique_adventure(_, []).
unique_adventure(U, [A|As]) :-
unique_elements(U, A),
unique_adventure(U, As).
unique_elements([], []).
unique_elements([U|Us], [A|As]) :-
U = A,
unique_elements(Us, As).
solve
是一个累加器,它通过检查相同位置的元素是否唯一来要求所有冒险列表的解是不同的。这一步就是为什么我选择在一个列表中包装冒险。
solve/2
的终止条件是任意的,但选择的结果是5个冒险的列表,每个冒险从一个猎人开始,否则solve
将迭代不同的解决顺序1。
make_adventure/2
将创建一个新的冒险NewA
,并将其与之前使用unique_adventure/2
创建的Adventures
列表进行比较。如果每个位置的元素不同,则冒险列表与其他列表不同,这由unique_elements/2
检查。
把它们放在一起就解决了这个难题(格式化输出以提高可读性):
?- solve(A).
A = [
[professor, stone, north_africa, bison],
[doctor, hands, central_africa, elephant],
[colonel, stick, west_africa, rhino],
[fire_chief, garment, south_africa, hippo],
[captain, gun, east_africa, puma]
] ;
false.
1使用内置程序,我很想写一个更通用的终端条件,可能会枚举像solve(A, A) :- findall(H, hunter(H), Hunters), maplist(nth0(0), A, Hunters).
优化性能,基于rayscan的答案:
invalid([doctor, _, east_africa, _]).
invalid([doctor, _, _, hippo]).
invalid([colonel, _, central_africa, _]).
invalid([_, gun, west_africa, _]).
invalid([_, garment, west_africa, _]).
invalid([_, stick, _, elephant]).
% valid conditions from the puzzle negated
invalid([H, T, _, _]) :-
+ ((H = professor, T = stone) ; (H = professor, T = stone)).
invalid([H, _, _, A]) :-
+ ((H = colonel, A = rhino) ; (H = colonel, A = rhino)).
invalid([_, T, P, _]) :-
+ ((T = hands, P = central_africa) ; (T = hands, P = central_africa)).
invalid([_, _, P, A]) :-
+ ((P = north_africa, A = bison) ; (P = north_africa, A = bison)).
invalid([H, _, P, _]) :-
+ ((H = fire_chief, P = south_africa) ; (H = fire_chief, P = south_africa)).
invalid([H, T, _, A]) :-
+ ((H = captain, T = gun, A = puma) ; (H = captain, T = gun, A = puma)).
go(Sol) :-
Hunters = [professor, doctor, colonel, fire_chief, captain],
Tools = [stone, hands, garment, gun, stick],
Places = [east_africa, central_africa, north_africa, south_africa, west_africa],
Animals = [rhino, hippo, bison, puma, elephant],
% Break symmetry by pre-specifying the hunters as the first element in the lists
findall([H, _, _, _], member(H, Hunters), Sol),
find_combs([Hunters, Tools, Places, Animals], Sol).
find_combs(CombLsts, [Comb|Sol]) :-
maplist(select, Comb, CombLsts, Rem),
+ invalid(Comb),
find_combs(Rem, Sol).
find_combs(CombLsts, _) :-
maplist(=([]), CombLsts).
结果swi-prolog:
?- time(findall(A, go(A), As)).
% 7,670 inferences, 0.005 CPU in 0.005 seconds (101% CPU, 1518784 Lips)
As = [[[professor,stone,north_africa,bison],[doctor,hands,central_africa,elephant],[colonel,stick,west_africa,rhino],[fire_chief,garment,south_africa,hippo],[captain,gun,east_africa,puma]]].
还有一个更优雅的答案:
valid_comb([professor, stone, _, _]).
valid_comb([colonel, _, _, rhino]).
valid_comb([_, hands, central_africa, _]).
valid_comb([_, _, north_africa, bison]).
valid_comb([fire_chief, _, south_africa, _]).
valid_comb([captain, gun, _, puma]).
invalid_comb([doctor, _, east_africa, _]).
invalid_comb([doctor, _, _, hippo]).
invalid_comb([colonel, _, central_africa, _]).
invalid_comb([_, gun, west_africa, _]).
invalid_comb([_, garment, west_africa, _]).
invalid_comb([_, stick, _, elephant]).
go(Sol) :-
Hunters = [professor, doctor, colonel, fire_chief, captain],
Tools = [stone, hands, garment, gun, stick],
Places = [east_africa, central_africa, north_africa, south_africa, west_africa],
Animals = [rhino, hippo, bison, puma, elephant],
CombLsts = [Hunters, Tools, Places, Animals],
% Break symmetry by pre-specifying the hunters as the first element in the lists
findall([H, _, _, _], member(H, Hunters), Sol),
% Do this only once
findall(V, valid_comb(V), Vs),
% Find a solution
find_combs(CombLsts, Vs, Sol).
find_combs(CombLsts, Vs, [Comb|Sol]) :-
% Select a combination, with remainder
maplist(select, Comb, CombLsts, Rem),
% Check validity each time, to fail quickly
+ invalid_comb(Comb),
is_valid_comb(Comb, Vs),
% Continue down this combination path
find_combs(Rem, Vs, Sol).
find_combs(CombLsts, _, _) :-
% Nothing left to try
maplist(=([]), CombLsts).
is_valid_comb(C, Vs) :-
% If any nonvar match, then all nonvar must match
maplist(nonvar_match_all_if_any(C), Vs).
nonvar_match_all_if_any(L1, L2) :-
% Unify both lists if they have any matching elements
(nonvar_match_any(L1, L2) -> L1 = L2 ; true).
nonvar_match_any([H1|T1], [H2|T2]) :-
% Is true if both elements are nonvar and equal
(H1 == H2 -> true ; nonvar_match_any(T1, T2)).
结果:
?- time(findall(A, go(A), As)).
% 17,779 inferences, 0.005 CPU in 0.005 seconds (99% CPU, 3291722 Lips)
As = [[[professor,stone,north_africa,bison],[doctor,hands,central_africa,elephant],[colonel,stick,west_africa,rhino],[fire_chief,garment,south_africa,hippo],[captain,gun,east_africa,puma]]].