统计一个子句被调用的次数

13

我有一个像下面这样的子句:

lock_open:-
        conditional_combination(X),
        equal(X,[8,6,5,3,6,9]),!,
        print(X).

这个子句会成功执行。但是我想知道在equal(X,[8,6,5,3,6,9])变为真之前,conditional_combination()被调用了多少次。程序根据一些规则生成排列,我需要知道生成特定值(如865369)所需的排列数量。

3个回答

12
你实际上想要的是略微不同的:你想要计算一个目标的答案数量(到目前为止)。下面的谓词 call_nth(Goal_0, Nth) 的成功与 call(Goal_0) 类似,但有一个额外的参数,指示找到的答案是第n个答案。该定义高度特定于SWI或YAP。在一般程序中不要使用像nb_setarg/3这样的东西,而是将其用于封装良好的情况,如此例。即使在这两个系统中,这些构造的精确含义对于一般情况也没有很好地定义。这里是SICStus的定义。更新:在较新版本中使用unsigned_64而不是unsigned_32
call_nth(Goal_0, Nth) :- nonvar(Nth), !, Nth \== 0, \+arg(Nth,+ 1,2), % produces all expected errors State = count(0,_), % note the extra argument which remains a variable Goal_0, arg(1, State, C1), C2 is C1+1, ( Nth == C2 -> ! ; nb_setarg(1, State, C2), fail ). call_nth(Goal_0, Nth) :- State = count(0,_), % note the extra argument which remains a variable Goal_0, arg(1, State, C1), C2 is C1+1, nb_setarg(1, State, C2), Nth = C2.

一个更加健壮的抽象由Eclipse提供:

call_nth(Goal_0, Nth) :-
   shelf_create(counter(0), CounterRef),
   call(Goal_0),
   shelf_inc(CounterRef, 1),
   shelf_get(CounterRef, 1, Nth).
?- call_nth(between(1,5,I),Nth).
   I = Nth, Nth = 1
;  I = Nth, Nth = 2
;  I = Nth, Nth = 3
;  I = Nth, Nth = 4
;  I = Nth, Nth = 5.

所以只需要简单地包装一下:

lock_open :-
   call_nth(conditional_combination(X), Nth),
   X = [8,6,5,3,6,9],
   !,
   ....

1
我看到一种使用这种原语可以在时间和空间上都以O(N)的方式进行聚合器的方法。谢谢! - CapelliC
需要重新考虑limit/2和offset/2的实现,也许更原始和通用的谓词将是call_nth/2。 - user502187
我没有意识到一个目标可以被称为那样(call_nth/2清单的第三行)。我以为总是需要call(Goal),但显然,只有Goal就足够了! - user1812457

4
如果你正在使用SWI Prolog,你可以使用nb_getval/2nb_setval/2来达到你想要的效果:
lock_open:- 
  nb_setval(ctr, 0),  % Initialize counter
  conditional_combination(X), 
  nb_inc(ctr),  % Increment Counter
  equal(X,[8,6,5,3,6,9]),
  % Here you can access counter value with nb_getval(ctr, Value)
  !, 
  print(X).

nb_inc(Key):-
  nb_getval(Key, Old),
  succ(Old, New),
  nb_setval(Key, New).

其他的Prolog实现有其他的方法来完成同样的任务,在您的Prolog实现中查找全局变量。在这个片段中,我使用术语ctr来保存当前目标计数器。您可以使用任何在程序中未使用的术语。


1
conditional_combination/1 中调用 nb_setval/2 将会影响结果。像 ctr 这样的名称可能会被用于其他计数... - false
@false:正确,OP 应该使用程序中未使用过的任何术语。 - gusbro
3
重点是你不能保证这一点,除非检查你整个程序。 - false
你说得没错,但如果你持有这种立场,那么你将永远不会使用assert/call/retract或者实际上任何具有全局影响的东西。事实上,就像在任何一种语言中一样,如果你使用任何全局变量,你都必须小心谨慎... - gusbro
我不同意:更新知识库需要类似于assert/retract的东西。而且有方法可以防止多次使用全局变量。以findall/3使用asserta/retract为例。您不能嵌套此类调用,也不能同时进行两个调用。 - false

0
在开发“micro”模块时,我最近发明了“pivots”。它们受到线程/管道模式的启发,用于传递数据。一个“pivot”是一个有界队列,最大长度为1,“pivot_put/1”也会复制给定术语。但出于性能原因,它们不使用同步并且是非阻塞的。
在某种程度上,它们与“nb_setarg/3”非常相似,只是它们不会破坏Prolog术语,而是更新Java数据结构。因此,它们比非逻辑术语操作更安全。此外,它们不需要一些“call_cleanup/3”,因为它们是由Java垃圾回收的。
在某种程度上,它们比“nb_setarg/3”更相似,而不是使用一些显式分配和释放结构的解决方案。因此,例如SICStus Prolog的解决方案可能是:
call_nth(Goal_0, Nth) :-
   new(unsigned_32, Counter),
   call_cleanup(call_nth1(Goal_0, Counter, Nth),
           dispose(Counter)).

call_nth1(Goal_0, Counter, Nth) :-
   call(Goal_0),
   get_contents(Counter, contents, Count0),
   Count1 is Count0+1,
   put_contents(Counter, contents, Count1),
   Nth = Count1.

有了枢轴,甚至没有32位的限制,我们可以直接进行:

call_nth(G, C) :-
   pivot_new(P),
   pivot_put(P, 0),
   call(G),
   pivot_take(P, M),
   N is M+1,
   pivot_put(P, N),
   C = N.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接