对于两个变量A
和B
,我具有以下条件:
[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
% expression 1
;
% expression 2
)
%cntd
问题出在第2行,求解器不知道A
和B
的值,如何确定在不指定变量值的情况下将继续哪个条件分支第2行?
合理的做法是,当求解器遍历变量的可能值时,根据变量的值决定该分支。 但是,正如我发现的那样,在知道变量的值之前,它会经过这些表达式之一。有什么方法可以防止这种情况发生?
答案 0 :(得分:1)
只要您坚持纯逻辑,约束编程就很适合Prolog。但是,正如您所演示的,不能随意将诸如 cut(!)和 if-then-else(->;)等程序元素与约束逻辑混合在一起。>
仅当在“约束设置时间”条件下(即无条件地为真)或无条件(无条件地为假)时,才使用if-then-else或cuts是安全的。实际上,这意味着此类条件不应包含问题变量(域变量等),而应仅包含先验已知的问题参数(常数)。专用的建模语言可以区分这两件事,但是Prolog不能。
如何在约束模型中不表达替代方案
以上表示您不能使用cut / if-then-else来表达您想要表达的替代形式。可能会很想摆脱条件的承诺选择方面,而将其重写为纯析取式。例如,您可以重写
( Usage #>= 1000 -> Rate#=7, Bonus#=100 % WRONG
; Rate#=9, Bonus#=0
)
纯属析取
( Usage #>= 1000, Rate#=7, Bonus#=100 % INEFFICIENT
; Usage #< 1000, Rate#=9, Bonus#=0
)
虽然这在逻辑上是正确的,但是不要这样做! Prolog通过回溯来探索替代方法(使用分号(;)或多个子句表示),即先急切地选择一个替代方法,然后再返回另一个方法。通常,这将破坏对有效约束程序的任何希望!在约束程序中,所有搜索都应位于搜索/标记例程中。
修正的约束条件
如果条件和替代方法的分支都是具有固定化实现的约束(即可以在布尔变量中反映约束的真相的实现),那么您很幸运:您可以重写整个条件替代方案借助用于限制约束的特殊连接词(在ECLiPSe中:and
,or
,neg
,=>
,#=
)。对于上面的示例:
Usage #>= 1000 => Rate#=7 and Bonus#=100, % OK
Usage #< 1000 => Rate#=9 and Bonus#=0
或
Usage #>= 1000 and Rate#=7 and Bonus#=100 or % OK
Usage #< 1000 and Rate#=9 and Bonus#=0
不幸的是,只有基本的算术约束具有统一化的版本,并且可以通过这种方式组合!
使用其他内置约束
从某种意义上说,处理替代方案是解决问题最困难的部分,许多内置约束可以解决此问题。因此,值得一查的是,是否可以在现有内置约束没有的基础上对问题进行建模,而在模型中没有任何明显的分离。候选人是 element/3,table/2。 disjunctive/2 和其他许多人。
延迟选择
最后的解决方法是将对替代方案的探索推迟到可以明确确定条件的真相之前。在ECLiPSe中,使用延迟子句最简单。以OP的示例为例:
delay choice(A, B) if var(A);var(B). % wait for A,B to be known
choice(A, B) :-
( (A>3 ; B>3) -> % can now use normal Prolog tests
write("expression 1")
;
write("expression 2")
).
这有效,但仅在实例化A和B之后才起作用。如果在这种情况下这种情况是可以纠正的,我们可以做得更好:
choice(A, B) :-
Bool #= (A#>3 or B#>3),
delayed_choice(Bool).
delay delayed_choice(Bool) if var(Bool).
delayed_choice(1) :- write("expression 1").
delayed_choice(0) :- write("expression 2").
当域满足条件时,这已经起作用:
?- choice(A, B), B #> 3.
expression 1
将一般析取关系转化为约束条件
ECLiPSe具有一个漂亮的功能,称为“ 通用传播” library(propia)。通过使用简单的注释,这可以有效地将Prolog析取关系变成约束。从上面的正确但效率低下的公式开始,我们可以编写:
?- ( Usage #>= 1000, Rate#=7, Bonus#=100
; Usage #< 1000, Rate#=9, Bonus#=0
) infers most.
Usage = Usage{-1.0Inf .. 1.0Inf}
Rate = Rate{[7, 9]}
Bonus = Bonus{[0, 100]}
There is 1 delayed goal.
Yes (0.00s cpu)
正如Rate
和Bonus
的域所示,甚至在可以确定适用的替代方案之前,就已经从析取物中提取了有用的信息。
答案 1 :(得分:-1)
出了什么问题?
重要说明是在if-else中使用->
(箭头)。当我们有表达式S -> T ; U
时,将对S
进行求值,如果它包含一些变量,可能会对代码产生一些副作用。为了更加清楚,请尝试运行一些示例:
?-[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
write("expression 1")
;
write("expression 2")
).
由于未确定A
和B
的值,因此条件始终为true,并且将打印expression 1
的值。而且,结果是:
A = A{1 .. 10}
B = B{1 .. 10}
There are 6 delayed goals.
Yes (0.00s cpu)
如您所见,A
和B
的边界不会改变,因为它被悬挂在将来的表达式中,而且正如我们所没有,边界也没有变化。
现在尝试其他示例:
?- [A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
write("expression 1")
;
write("expression 2")
),
A = 3. % this line is added
确定A
的值时,A #> 3
不正确,但是B
呢?是等于3还是大于3?正如我们所说,条件部分将被执行!因此,对B
的最后一个约束是B #> 3
。并且除了执行write("expression 1")
之外,结果是:
A = 3
B = B{4 .. 10}
Yes (0.00s cpu)
最后一个例子是:
?- [A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
write("expression 1")
;
write("expression 2")
),
A = 3,
B = 3. % this line is added
同样在此示例中,打印expression 1
,结果是:
No (0.00s cpu)
这是因为将执行箭头的箭头和始终为expression 1
的事实。
一种解决方案正在使用;
运算符,如下所示:
[A,B] #:: 1..10,
(
(A = 3, B = 3, write('expression 21'))
;
(A = 3, B #> 3, write('expression 11'))
;
(A #> 3, B #> 3, write('expression 12'))
;
(A #> 3, B = 3, write('expression 13'))
),
A = 3,
B = 5.
以上代码的结果是:
A = 3
B = 5
Yes (0.00s cpu, solution 1, maybe more)
它会打印:
expression 21 expression 11
这意味着第一个分支关闭,但失败并自动回溯,并转到下一个情况!然后在下一种情况下,一切都将在那里成功!