Prolog:模拟析取事实

Posted

技术标签:

【中文标题】Prolog:模拟析取事实【英文标题】:Prolog: simulate disjunctive facts 【发布时间】:2015-12-03 19:36:33 【问题描述】:

我有一个想解决的逻辑问题,所以我想,“我知道,我会试试 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 的任务是了解 Bob 持有什么牌。正如人们所预料的那样,Charlie 是一个自动化系统。

爱丽丝问鲍勃“你有 1 还是 2?”作为回应,鲍勃向爱丽丝展示了一张卡片。 Charlie 现在得知 Bob 拥有 1 或 2。

Alice 然后问“你有 2 还是 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;查理可以推导出来。

【问题讨论】:

请提出具体问题。编写一些伪代码,并给我们一个预期的输出(或操作)。 您可以将其声明为one_of_three(A):- memberchk(A,[a,b,c]). 规则。 @Joost 我们已经问过你好几次了,很抱歉不得不重复这个问题,请向我们提供一个特定的示例查询及其预期输出。不用担心语法,把它完整地写下来。使用我建议的规则定义,示例查询将是?- one_of_three(A). 结果是A=a,逻辑上与true 相同。或者你可以问?- A=c, one_of_three(A). 为了完整起见,我也会在这里重复我对答案的回复,因为我觉得它也与这方面有关。解决方案不一定需要是交互式程序;添加关于 Bob 卡片的新观察可以添加事实,从头开始以不同的方式填充数据库,而不是添加规则来修改状态。 你所指的书是 1988 年出版的。关于 Prolog 中高阶谓词支持的声明已经过时了(准确地说是在 2012 年)。 【参考方案1】:

直接回答您的问题

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

在三个变量 X、Y、Z 中,恰好一个可以在域 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) 为真,foo(b) 为真,foo(c) 为真。

但是,如果我理解您的问题,您需要一条规则,例如:

恰好有一个foo(a)foo(b)foo(c) 为真。

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

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

编辑问题后编辑

这里是使用有限域上的约束规划来解决该特定问题的方法,其中 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.

如果这组牌是 1,2,3,4,并且 Bob 拿着两张牌,当 Alice 问“你有 1 还是 2”时,他说“是”,而当她问“做你有 2 或 3”,他说不,然后:查理能知道 Bob 拿着 1 吗?

答案是:

是的,如果 Bob 拿着 1,另一张牌是 4;没有其他可能的解决方案。

或者:

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

同上,Charlie 能知道 Bob 拿着 3 吗?

查理肯定知道鲍勃没有拿着三!

这是什么意思?

:- 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,答案是'是'”。

然后,我们说 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))).

【讨论】:

我应该更清楚;这不是析取的规则,而是我试图引入的事实。我现在试着用一个例子来说明。 啊,我想我还是不够清楚,因为无论我转向哪个方向,这是我们正在解决的另一个问题。我将添加更多上下文。 @Joost 这就是 SO 指南要求提问者提出具体问题的原因。 :) 你最初的直觉是不正确的(恕我直言);你根本不应该用 Prolog 数据库事实来代表你的知识,因为在 Prolog 中动态数据库编程是不受欢迎的。 很公平,这是有道理的。不过,我不介意把它变成一个静态的、声明性的东西,添加新的事实意味着添加代码并重新运行整个程序。这仍然使它成为一个动态数据库吗? 对,这可能是查询的一部分,但我认为它可能是声明的数据库的一部分,(允许我使用 Prolog 自己解析查询的方式,对吗?)。 . 为什么不应该呢?【参考方案2】:

vanilla 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 是图灵完备的,所以一般来说,当有人说“它不能在 Prolog 中完成”时,他们通常的意思是“它涉及一些额外的工作”。

【讨论】:

很好的答案 (+1)。您能否扩展程序,使其也可以到达“Bob 也拥有 4”? 现在这正是我一直在寻找的东西。漂亮干净,让像我这样的绝对新手可以很容易地看到发生了什么。我想看看你对将 Bob 的手牌限制在1,2,3,4 的两张牌以推动owns(bob, 4) 的想法,确实如此!【参考方案3】:

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

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。顺便说一句,在尝试编写此代码时,我注意到原始问题陈述中存在以下歧义:

爱丽丝问鲍勃“你有 1 还是 2?”作为回应,鲍勃向爱丽丝展示了一张卡片。 Charlie 现在得知 Bob 拥有 1 或 2。

现在这是否意味着 Bob 正好有 一个 1 和 2,或者他可能持有 其中一个或两个

PS

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

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

【讨论】:

我打算让 Bob 持有“一个或两个”。我会澄清问题中的歧义。 @Joost 您仍然不需要此信息来推断 Bob 持有 1 和 4。您甚至不需要知道他正好持有两张牌。 .. 嗯,你说得对——我想挑选一个以最小方式正确涵盖所有属性的玩具示例并非易事;-) @Joost 我的意思是“规范化”,就像在关系数据库中一样。 Prolog 数据库有点像关系数据库(不完全是,但我不想进入那个讨论)。例如,answer by @peak 在“规范化”包含问题和答案的表格方面走得更远。 @Joost 如果我不得不总结一下,带回家的信息是:Prolog 是一种通用编程语言,每当您阅读类似于“X 的内容时,由于 Prolog 的 Y 限制,这是不可能的”,您的 BS 检测器应该开始发出哔哔声。【参考方案4】:

(哎呀,我才意识到这已经 6 岁了,但是为下一个绊脚石引入带有概率选择的逻辑编程语言可能会很有趣)

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

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

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 更难理解,但我发现这种方法是表达问题的一种有趣方式。计算也更难,但这并不一定是用户必须担心的。

【讨论】:

以上是关于Prolog:模拟析取事实的主要内容,如果未能解决你的问题,请参考以下文章

Prolog图表表示缺少事实

Swi-prolog 双向子句/事实

在prolog中,是否可以将一个列表作为一个事实?

检测和删除最少数量的不一致事实的算法(可能在 PROLOG 中)?

SWI Prolog程序可以从外部数据库获取其事实吗?

prolog表示不等式