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/1
和 holds_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 更难理解,但我发现这种方法是一种表达问题的有趣方式。计算也更难,但这不一定是用户必须担心的事情。
我有一个逻辑问题想解决,所以我想,"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, andfoo(b)
is true, andfoo(c)
is true.
但是,如果我理解你的问题,你想要一个规则,例如:
exactly one of
foo(a)
,foo(b)
, andfoo(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/1
和 holds_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 更难理解,但我发现这种方法是一种表达问题的有趣方式。计算也更难,但这不一定是用户必须担心的事情。