【问题标题】:Space-efficient immutable map implementation in Prolog?Prolog 中节省空间的不可变映射实现?
【发布时间】:2016-12-12 14:19:39
【问题描述】:

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

上下文

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

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

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

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

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

  • 对于任何新生成的状态,从所述状态计算哈希值(通过任何方式),并将 (hash,state) 对作为 (key,value) 插入到映射中。
  • 要检查一个状态是否之前已经见过,计算它的哈希值并检查对应于该哈希值的条目是否存在于映射中。 (在本练习中,我们忽略了哈希冲突的可能性)

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

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

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

map_new(?Map)  

...将 Map 与空地图统一。

map_get(+Map,+Key,?Val)  

...将 Val 与映射 Map 中的键 Key 关联的值统一起来,或者如果 Map 不包含这样的 Key 则失败(通过允许 Key 为变量,可以通过回溯实现枚举)。

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

...将映射NewMap与将(Key,Val)对插入映射Map得到的映射统一。如果map Map中已经包含一个key为Key的entry,则该entry的值与OldVal统一。

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

...将映射NewMap 与通过从映射Map 中移除键Key 的条目获得的映射统一。 map Map 中key Key 关联的值与OldVal 统一。如果地图 Map 不包含键 Key 则失败。

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

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 谓词。

问题

此时,我们需要为地图选择一个好的实现。我们不希望在删除或添加单个项目时过度更改结构,以便底层实现可以通过在构造 MM 时引用映射 M 的子结构(例如子树)来保持复制操作和通常堆空间较低在map_put/5map_rem/4 期间从中获取。

(例如,我听说 Clojure 对它的一些 4[不可变映射] 使用了这种意义上的高效实现,但我没有仔细研究过。)

在 SWI Prolog 中,我们有 AVL-tree 支持的 assoc 实现。这非常适合快速查找。不幸的是,每当插入或删除树节点时,AVL 树可能会发生很大变化。映射MMMMMM 或介于两者之间的任何映射的实际堆上布局之间可能没有太多共同点:堆可能会迅速填满。 assoc 的实现在 Prolog btw 中。 (/usr/lib64/swipl-7.2.3/library/assoc.pl),里面没有特别的酱料。

我是否误以为基于 AVL 树的地图无法胜任这项任务?

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

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

【问题讨论】:

  • 那么具体在哪里?
  • 好的,谢谢假。我的直觉还很薄弱。

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


【解决方案1】:

我是否误以为基于 AVL 树的地图无法胜任这项任务?

很有可能:

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

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

另请注意,许多其他平衡树结构(红黑树等)也承认自然和 Prolog 实现,如果 AVL 树结果不是很适合你,我建议你看看一些纯粹的替代品。

Chris Okasaki 的博士论文Purely Functional Data Structures 在这种情况下非常值得一看。

【讨论】:

猜你喜欢
  • 2021-09-12
  • 1970-01-01
  • 1970-01-01
  • 2018-11-21
  • 1970-01-01
  • 1970-01-01
  • 2017-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多