Prolog中节省空间的不可变地图实现?

时间:2016-12-12 14:19:40

标签: prolog swi-prolog avl-tree memory-efficient

这是一个关于地图数据结构的问题,它在严重的非破坏性操作下保持内存效率。

上下文

我正在编写一个小程序,根据一些逻辑,我可以在谓词iterate/2中生成“状态”(它们只是保存数据的术语),这些逻辑可以在本讨论中被忽略。

iterate/2以递归方式递归调用自身。在每次调用时,iterate/2会生成更多状态,并且必须有效地检查以前是否已经看到任何状态。

“先前见过”的州数量可能相当大(数万)。但是,我们可以不时“忘记”以前看过的条目(很少一部分)。这是一件好事,因为我们希望将整体内存需求保持在界限范围内。

为了有效执行“先前看到的”谓词,由适当的实现支持的“map”接口似乎是一个不错的选择(因为可以有效地在map中实现查找)。

假设我们有这样的地图数据结构。

  • 对于任何新生成的状态,从所述状态计算哈希值(通过任何方式),并将该对(哈希,状态)作为(键,值)插入到地图中。
  • 要检查以前是否已经看过状态,请计算其哈希值并检查地图中是否存在与该哈希值对应的条目。 (对于本练习,我们忽略了哈希冲突的可能性)

然后在iterate的调用之间传递不同的地图数据结构。

这是一种逻辑编程语言,我们希望保持在所有可能的定义不可变结构的永恒数学空间中工作的一般概念,我们从一个结构移动到另一个结构,就像一只从藤蔓中摆动的猴子而不是像在命令式编程中那样不断地改变时变数据结构。我们只希望垃圾收集器比我们快!

我们提供的与地图相关的谓词可能是:

map_new(?Map)  

...将地图与空地图统一起来。

map_get(+Map,+Key,?Val)  

...使用与地图Map中的键Key相关联的值来统一Val,如果Map不包含此类Key,则会失败(通过允许Key为变量,可以通过回溯实现枚举)。

map_put(+Map,+Key,?Val,?OldVal,?NewMap)

...将地图NewMap与通过将(Key,Val)对插入到地图Map中获得的地图统一。如果地图Map已包含带有键Key的条目,则所述条目的值与OldVal统一。

map_rem(+Map,+Key,?OldVal,?NewMap) 

...将地图NewMap与通过从地图Map中删除具有键Key的条目获得的地图统一。与Map中的键Key关联的值与OldVal统一。如果地图地图不包含密钥,则会失败。

我们会像上面这样使用上面的内容:

iterate(Solution) :-
   map_new(Map),
   iterate(Map,Solution).

iterate(M,S) :-
   gen_fresh_states(ListFresh),
   gen_stale_states(ListStale),
   add_fresh_states(M,ListFresh,MM),
   del_stale_states(MM,ListStale,MMM),
   ( termination_check(MMM,S) -> true ; iterate(MMM,S) ).

其中

 add_fresh_states(M,ListFresh,MM),
 del_stale_states(MM,ListStale,MMM),

以预期的方式迭代状态列表ListFresh和ListStale,迭代地调用map_put/5map_rem/4

-> ;->/2谓词。

问题

此时,我们需要为地图选择一个好的实现。我们不希望每当删除或添加单个项时,结构都会发生过度变化,因此,只要{{1},就可以通过引用map M的子结构(例如子树)来保持复制操作和通常堆空间不足。在MMmap_put/5期间构建它。

(例如,我听说Clojure在某种意义上对其4 [不可变映射]使用了有效的实现,但我没有密切关注它。)

在SWI Prolog中,我们有AVL-tree支持的assoc实施。这非常适合快速查找。不幸的是,无论何时插入或移除树节点,AVL树都可能发生重大变化。地图map_rem/4MMM的实际堆上布局或其间的任何地图之间可能没有太多共同之处:堆可能会快速填满。 MMM的实现在Prolog btw中。 (assoc),没有特别的酱汁。

我错误地认为基于AVL树的地图不能胜任这项任务吗?

或者,是否有任何地图实现(不一定在SWI Prolog中)能够有效地重用子结构?

我想通过使用“!”已经可以减轻堆的使用在/usr/lib64/swipl-7.2.3/library/assoc.pl的子目标之间。这将告诉运行时不会发生回溯,从而赋予它许可来销毁任何不会再次使用的堆上结构。例如,“!”调用iterate/2之后可能使del_state_states/3有资格进行垃圾收集(我不知道这是否真的有效)。

1 个答案:

答案 0 :(得分:2)

  

我错误地认为基于AVL树的地图不能胜任这项任务吗?

很有可能:

根据我的经验,library(assoc)是许多需要按照您的描述进行访问的任务的优秀选择。我建议你试一试!

在实践中,对数开销通常非常可接受,您也可以经常从头开始重新构建地图以删除不需要的元素。

另请注意,许多其他平衡树结构(红黑树等)也允许自然和 Prolog实现,如果AVL树结果对你有用,我建议你查看一些纯粹的替代品。

克里斯·冈崎的博士论文Purely Functional Data Structures在这种情况下值得一看。