Prolog:模拟析取事实

Prolog: simulate disjunctive facts

我有一个逻辑问题想解决,所以我想,"I know, I'll try Prolog!"

不幸的是,我 运行 几乎立刻就撞到了一堵砖墙。所涉及的假设之一是析取事实; A、B 或 C 之一为真(或不止一个),但我不知道是哪一个。我后来才知道这是 something Prolog does not support.

有很多文档似乎都在解决这个问题,但其中大部分似乎立即涉及更复杂的概念并解决更高级的问题。我正在寻找的是一种独立的方式来模拟定义上述事实(由于 Prolog 的限制,直接定义它是不可能的)。

我该如何解决这个问题?我能以某种方式将其包装在规则中吗?

编辑: 我意识到我不是很清楚。鉴于我对 Prolog 不熟悉,我不想在尝试传达问题时陷入语法错误,而是选择了自然语言。我想那没有成功,所以无论如何我都会在伪 Prolog 中试一试。

直觉上,我想做的是这样的,声明 foo(a)foo(b)foo(c) 成立,但我不知道哪个:

foo(a); foo(b); foo(c).

那么我希望得到以下结果:

?- foo(a); foo(b); foo(c).
true

不幸的是,我试图声明的事实(即 foo(x) 至少适用于一个 x \in {a, b, c})不能这样定义。具体来说,它会导致 No permission to modify static procedure '(;)/2'.

旁注:在声明析取事实后,?- foo(a). 的结果从逻辑角度来看对我来说有点不清楚;它显然不是 true,但 false 也没有涵盖它——在这种情况下,Prolog 根本没有足够的信息来回答该查询。

编辑 2: 这里有更多的上下文,使它更接近真实世界的场景,因为我可能在翻译中过度简化并丢失了细节。

假设涉及三个人。爱丽丝、鲍勃和查理。 Bob 持有该组 {1, 2, 3, 4} 中的两张牌。爱丽丝问他问题,作为回答,他向她展示了一张查理没有看到的卡片,或者没有展示卡片。如果有更多卡片适用,Bob 只显示其中一张。查理的任务是了解鲍勃手里拿着什么牌。正如人们所料,Charlie 是一个自动化系统。

Alice 询问 Bob "Do you have a 1 or a 2?",作为回应,Bob 向 Alice 出示了一张卡片。 Charlie 现在得知 Bob 拥有 1 或 2。

Alice 然后问 "Do you have a 2 or a 3",Bob 没有牌可以出示。很明显,Bob 有一个 1,他之前给 Alice 看了。基于这两个事实,查理现在应该能够得出这一点。

我试图建模的是 Bob 拥有 1 或 2 (own(Bob, 1) \/ own(Bob, 2)),并且 Bob 不拥有 2 或 3 (not (own(Bob, 2) \/ own(Bob, 3))) 的知识。查询 Bob 是否拥有 1 现在应该是 true;查理可以推导出这个。

您问题的直接答案

如果您可以使用有限域上的约束逻辑编程对您的问题进行建模,则可以使用 #\ 实现“异或”,如下所示:

Of the three variables X, Y, Z, exactly one can be in the domain 1..3.

D = 1..3, X in D #\ Y in D #\ Z in D

要概括这一点,您可以这样写:

disj(D, V, V in D #\ Rest, Rest).

vars_domain_disj([V|Vs], D, Disj) :-
    foldl(disj(D), Vs, Disj, V in D #\ Disj).

并将其用作:

?- vars_domain_disj([X,Y,Z], 2 \/ 4 \/ 42, D).
D = (Y in 2\/4\/42#\ (Z in 2\/4\/42#\ (X in 2\/4\/42#\D))).

如果您不使用 CLP(FD),例如您无法在您的问题和整数之间找到一个很好的映射,您可以做其他事情。假设你的变量在列表 List 中,其中任何一个,但恰好是一个,可以是 foo,其余的不能是 foo,你可以说:

?- select(foo, [A,B,C], Rest), maplist(dif(foo), Rest).
A = foo,
Rest = [B, C],
dif(B, foo),
dif(C, foo) ;
B = foo,
Rest = [A, C],
dif(A, foo),
dif(C, foo) ;
C = foo,
Rest = [A, B],
dif(A, foo),
dif(B, foo) ;
false.

查询是这样的:在列表[A,B,C]中,其中一个变量可以是foo,那么其余的必须不同于foo。您可以看到该查询的三种可能解决方案。

原回答

可悲的是,人们经常声称 Prolog 不支持这样或那样的东西;通常,这不是真的。

你的问题目前还不是很清楚,但是你的意思是,对于这个程序:

foo(a).
foo(b).
foo(c).

您得到以下查询答案:

?- foo(X).
X = a ;
X = b ;
X = c.

您可能将其解释为:

foo(a) is true, and foo(b) is true, and foo(c) is true.

但是,如果我理解你的问题,你想要一个规则,例如:

exactly one of foo(a), foo(b), and foo(c) can be true.

但是,根据上下文,它、程序的其余部分和查询,原始解决方案可能正是这个意思!

但您的问题确实需要更具体,因为解决方案将取决于它。

编辑问题后编辑

这里有一个解决特定问题的方法,它使用有限域上的约束编程和伟大的 library(clpfd) by Markus Triska,在 SWI-Prolog 中可用。

完整代码如下:

:- use_module(library(clpfd)).

cards(Domain, Holds, QAs) :-
    all_distinct(Holds),
    Holds ins Domain,
    maplist(qa_constraint(Holds), QAs).

qa_constraint(Vs, D-no) :-
    maplist(not_in(D), Vs).
qa_constraint([V|Vs], D-yes) :-
    foldl(disj(D), Vs, Disj, V in D #\ Disj).

not_in(D, V) :- #\ V in D.

disj(D, V, V in D #\ Rest, Rest).

以及两个示例查询:

?- cards(1..4, [X,Y], [1 \/ 2 - yes, 2 \/ 3 - no]), X #= 1.
X = 1,
Y = 4 ;
false.

If the set of cards is {1,2,3,4}, and Bob is holding two cards, and when Alice asked "do you have 1 or 2" he said "yes", and when she asked "do you have 2 or 3" he said no, then: can Charlie know if Bob is holding a 1?

答案是:

Yes, and if Bob is holding a 1, the other card is 4; there are no further possible solutions.

或者:

?- cards(1..4, [X,Y], [1 \/ 2 - yes, 2 \/ 3 - no]), X #= 3.
false.

Same as above, can Charlie know if Bob is holding a 3?

Charlie knows for sure that Bob is not holding a three!

这是什么意思?

:- use_module(library(clpfd)).

使库可用。

cards(Domain, Holds, QAs) :-
    all_distinct(Holds),
    Holds ins Domain,
    maplist(qa_constraint(Holds), QAs).

这定义了我们可以从顶层查询的规则。第一个参数必须是一个有效的域:在您的例子中,它将是 1..4 表示卡片在集合 {1,2,3,4} 中。第二个参数是一个变量列表,每个变量代表 Bob 持有的一张牌。最后是“问题”和“答案”的列表,每一个都是Domain-Answer的格式,所以1\/2-yes的意思是“对于这个问题,你持有1还是2,答案是'yes'".

然后,我们说 Bob 持有的所有卡片都是不同的,并且每张卡片都是集合中的一张,然后我们将每个问答对映射到卡片上。

qa_constraint(Vs, D-no) :-
    maplist(not_in(D), Vs).
qa_constraint([V|Vs], D-yes) :-
    foldl(disj(D), Vs, Disj, V in D #\ Disj).

“否”的答案很简单:只要说 Bob 持有的每张牌,在提供的域中 不是 #\ V in D

not_in(D, V) :- #\ V in D.

“是”的答案意味着我们需要一张排他性的或 Bob 持有的所有牌; 2\/3-yes 应该导致“第一张牌是 2 或 3,或者第二张牌是 2 或 3,但不是两者都!”

disj(D, V, V in D #\ Rest, Rest).

要理解最后一个,试试:

?- foldl(disj(2\/3), [A,B], Rest, C in 2\/3 #\ Rest).
Rest = (A in 2\/3#\ (B in 2\/3#\ (C in 2\/3#\Rest))).

普通 Prolog 中的生成和测试解决方案:

 card(1). card(2). card(3). card(4).

 owns(bob, oneof, [1,2]).  % i.e., at least one of

 owns(bob, not, 2).

 owns(bob, not, 3).

 hand(bob, Hand) :-
   % bob has two distinct cards:
   card(X), card(Y), X < Y, Hand = [X, Y],
   % if there is a "oneof" constraint, check it:
   (owns(bob, oneof, S) -> (member(A,S), member(A, Hand)) ; true),
   % check all the "not" constraints:
   ((owns(bob, not, Card), member(Card,Hand)) -> false; true).

使用上面的文字记录:

$ swipl
['disjunctions.pl'].
% disjunctions.pl compiled 0.00 sec, 9 clauses
true.

?- hand(bob,Hand).
Hand = [1, 4] ;
;
false.

注意 Prolog 是图灵完备的,所以一般来说,当有人说 "it can't be done in Prolog" 时,他们通常指的是 "it involves some extra work".

为了方便,这里有一个小程序:

card(1). card(2). card(3). card(4). % and so on

holds_some_of([1,2]). % and so on

holds_none_of([2,3]). % and so on

holds_card(C) :-
    card(C),
    holds_none_of(Ns),
    \+ member(C, Ns).

我省略了谁拥有什么等等。我没有故意标准化 holds_some_of/1holds_none_of/1

这实际上足以满足以下查询:

?- holds_card(X).
X = 1 ;
X = 4.

?- holds_card(1).
true.

?- holds_card(2).
false.

?- holds_card(3).
false.

?- holds_card(4).
true.

这表明您甚至不需要知道 Bob 拿着 1 或 2。顺便说一句,在尝试编写代码时,我注意到原始问题陈述中存在以下歧义:

Alice asks Bob "Do you have a 1 or a 2?", in response to which Bob shows Alice a card. Charlie now learns that Bob owns a 1 or a 2.

这是否意味着 Bob 恰好拥有 1 和 2 中的 一个,或者他可以持有 其中一个或两个卡片?

PS

上面的小程序其实可以简化为如下查询:

?- member(C, [1,2,3,4]), \+ member(C, [2,3]).
C = 1 ;
C = 4.

(Eep,我才意识到这已经 6 岁了,但也许为下一个绊脚石引入具有概率选择的逻辑编程语言很有趣)

我会说接受的答案是最正确的,但如果有人对概率感兴趣,那么 problog 等 PLP 语言可能会很有趣:

这个例子假设我们不知道鲍勃有多少张牌。固定数量的卡可以轻松修改。

card(C):- between(1,5,C). % wlog: A world with 5 cards

% Assumption: We don't know how many cards bob owns. Adapting to a fixed number of cards isn't hard either
0.5::own(bob, C):-
    card(C).
    
pos :- (own(bob,1); own(bob,2)).

neg :- (own(bob,2); own(bob,3)).

evidence(pos).   % tells problog pos is true.
evidence(\+neg). % tells problog neg is not true.

query(own(bob,Z)).

在线试用:https://dtai.cs.kuleuven.be/problog/editor.html#task=prob&hash=5f28ffe6d59cae0421bb58bc892a5eb1

尽管 problog 的语义比 prolog 更难理解,但我发现这种方法是一种表达问题的有趣方式。计算也更难,但这不一定是用户必须担心的事情。