Prolog中的动态联合查找算法

问题描述

假设我有集合S1,..,Sn,我们想找到 最小的盖子C1,..,Cm,因此在每个盖子中都不会相交 连接的组件。

例如,对于集合S1 = [X,Y],S2 = [Y,Z],S3 = [T] I 将找到封面C1 = [X,Y,Z]和C2 = [T]。关于什么 可以动态分割封面的动态算法?

假设元素Y死了,那么我们剩下 S1'= [X],S2'= [Z],S3'= [T]。现在是封面 C1'= [X],C2'= [Z],C3'= [T]。所以封面数 增加了。

联合查找算法可以确定给定的覆盖范围 集的集合,但恐怕每当元素死亡时重新计算完整集合的效率都不高。

解决方法

要利用Prolog系统的资源,我做了一个基于copy_term / 2和keysort / 2的联合查找算法。算法here的主要入口点执行以下操作:

<div id="CaixaGaleria" class="gallery">
  <div data-tecnica="lapis" data-estilo="retrato" data-tamanho="pequeno"><img src="Imagens/Filme/14.jpg"></div>
  <div data-tecnica="lapis" data-estilo="retrato" data-tamanho="medio"><img src="Imagens/Filme/16.jpg"></div>
  <div data-tecnica="lapis" data-estilo="retrato" data-tamanho="grande"><img src="Imagens/Filme/17.jpg"></div>
  <div data-tecnica="aguarela" data-estilo="paisagem" data-tamanho="pequeno"><img src="Imagens/Filme/20.jpg"></div>
  <div data-tecnica="aguarela" data-estilo="paisagem" data-tamanho="medio"><img src="Imagens/Filme/22.jpg"></div>
  <div data-tecnica="aguarela" data-estilo="paisagem" data-tamanho="grande"><img src="Imagens/Filme/27.jpg"></div>
  <div data-tecnica="oleo" data-estilo="ilustração" data-tamanho="pequeno"><img src="Imagens/Filme/37.jpg"></div>
  <div data-tecnica="oleo" data-estilo="ilustração" data-tamanho="medio"><img src="Imagens/Filme/42.jpg"></div>
  <div data-tecnica="oleo" data-estilo="ilustração" data-tamanho="grande"><img src="Imagens/Filme/53.jpg"></div>
</div>

这是一个示例运行:

covers(L,S) :-
   vars_list(L,K),copy_term(K,R),make_keys(L,R,H),keysort(H,J),collect_keys(J,S).

要获得动态算法,我们可以尝试以下方法。保持可追溯的结构,该结构允许从元素中查找封面。然后,如果某个元素死亡,则仅重新计算属于该死亡元素的掩盖。

这将稍微降低复杂度。否则,除了观察到死亡元素只会将其自身的封面分成较小的封面之外,我这里没有更好的主意了。

,

我认为这有两个困难的部分:

  • 非常有效的地图数据结构
  • 一种有效的数据结构,用于跟踪要重新计算的内容

联合查找数据结构本身已在Prolog中实现,但隐藏在“变量”这个不太清晰的名称后面。如果您有一种将术语与代表其并集查找对等类的变量相关联的方法,则:

  • find操作是对类变量的查找
  • 两个类是否相同的测试是Class1 == Class2
  • union操作是Class1 = Class2

所以find是瓶颈。在命令式语言中,查询通常是恒定时间,使用foo.equivalence_classequivalence_classes[foo.id]之类的东西。通常,在Prolog中,我们没有等效的(近)恒定时间映射。但是由于您似乎只对变量感兴趣,所以SWI-Prolog的attributed variables确实适合您!

我们可以为像这样的术语列表计算联合查找对等类:

compute_classes([]).
compute_classes([Term | Terms]) :-
    term_variables(Term,Variables),variables_class(Variables,_NewEquivalenceClass),compute_classes(Terms).

variables_class([],_EquivalenceClass).
variables_class([Var | Vars],EquivalenceClass) :-
    (   get_attr(Var,equivalence_class,ExistingEquivalenceClass)
    ->  ExistingEquivalenceClass = EquivalenceClass
    ;   put_attr(Var,EquivalenceClass) ),variables_class(Vars,EquivalenceClass).

使用您的示例:

?- compute_classes([X+Y,Y+Z,T]).
put_attr(X,_2772),put_attr(Y,put_attr(Z,put_attr(T,_2814).

我们可以看到XYZ都共享一个等效类,而T在一个单独的类中。

一些实用程序:

var_class(Var,Class) :-
    get_attr(Var,Class).

var_var_sameclass(Var1,Var2) :-
    var_class(Var1,Class1),var_class(Var2,Class2),Class1 == Class2.

var_var_union(Var1,Class1 = Class2.

继续示例:

?- compute_classes([X+Y,T]),var_class(X,ClassX),var_class(Y,ClassY),var_class(T,ClassT).
ClassX = ClassY,put_attr(X,ClassT).

?- compute_classes([X+Y,var_var_sameclass(X,Y).
put_attr(X,_3436),_3478).

?- compute_classes([X+Y,T).
false.

?- compute_classes([X+Y,var_var_union(Z,T),T). 
put_attr(X,_3502),_3502).

也就是说,XY实际上属于同一类,而XT不是同一类。如果我们合并ZT的类,那么突然之间XT属于同一类。

杀死变量变得更加乏味。这里的想法(如您在问题中所建议的)是仅重新计算输入的“受影响”部分。我认为这可以通过将一组受影响的术语与每个等价类相关联来完成。我将在此处使用列表,但我不建议列表在实践中是一个不错的选择。

计算术语列表的等效类,以及每个等效类的“监视列表”:

compute_classes_and_watchlists(Terms) :-
    compute_classes(Terms),maplist(compute_watchlist,Terms).

compute_watchlist(Term) :-
    term_variables(Term,[RepresentativeVariable | _OtherVars]),var_class(RepresentativeVariable,Class),(   get_attr(Class,class_watchlist,Watchlist)
    ->  true
    ;   Watchlist = [] ),put_attr(Class,[Term | Watchlist]).

例如:

?- compute_classes_and_watchlists([X+Y,_2932),put_attr(_2932,[Y+Z,X+Y]),_3012),put_attr(_3012,[T]).

因此,如果您要杀死XYZ中的任何一个,则他们的课程_2932的监视列表将告诉您,您需要重新计算等效项术语Y+ZX+Y的类(但没有其他内容)。

杀死自己会获取被杀死变量的类及其监视列表(“返回”)并清除该类中每个变量的等效类:

kill_var(Var,TermsToRecompute) :-
    var_class(Var,get_attr(Class,TermsToRecompute),del_attr(Class,class_watchlist),maplist(clear_class,TermsToRecompute).

clear_class(Term) :-
    term_variables(Term,del_attr(RepresentativeVariable,equivalence_class).

仅当您立即(a)将被杀死的变量绑定到基本术语,并且(b)重新计算受影响术语的等价类时,杀死才有意义。在您的示例中:

?- compute_classes_and_watchlists([X+Y,kill_var(Y,Y = y_is_now_bound,compute_classes_and_watchlists(TermsToRecompute).
Y = y_is_now_bound,TermsToRecompute = [y_is_now_bound+Z,X+y_is_now_bound],_4640),put_attr(_4640,[X+y_is_now_bound]),_4674),put_attr(_4674,[y_is_now_bound+Z]),_4708),put_attr(_4708,[T]).

这变得很难理解,但要点是,在杀死并绑定Y并根据受影响的术语重新计算并发现结构后,XZ现在处于单独的等效类。 T只是不受影响。

尽管我对SWI归因于var docs的阅读是,这甚至都不会做任何非常错误的事情,但所有这些假设都假设您不会在一个联合中回溯。由于put_attr是可回溯的,因此在更谨慎的情况下,这可能会成为相当灵活的实现,其中回溯只是将类再次分开。我认为,在一次杀戮中回溯也是可行的。

待办事项:

  • 在监视列表的情况下,必须更改var_var_union的定义以合并两个类的监视列表(如果它们是不同的);对于实际的列表,这将是append,但是某些实际的集合或更专业的数据结构会更好,特别是如果您期望具有某种“类似堆栈”的行为,而其中下一个变量很可能被杀死您最近对其进行过union操作
  • 的操作
  • 尤其是使用监视列表方法,必须防止用户意外地统一equivalence_class术语;这可以通过使用某种class(<unique_id>,NakedClassVariable)而不是仅使用裸变量来表示等价类来完成
  • 重复调用term_variables可能可以通过某种方式进行优化-除了术语的监视列表之外,您还可以保留感兴趣的变量的监视列表

总而言之,这还不是生产准备就绪的代码,但可能会给您一些想法。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...