跳转到内容

Prolog/关联映射

来自 Wikibooks,开放世界中的开放书籍
示例仅包含整数键的二叉搜索树

Prolog 的内置列表很方便,但有时简单的线性列表不够用。当您想要维护一组元素(“键”)与另一组元素(“值”)之间的关联时,您需要一个关联映射数据结构。以下是使用 二叉搜索树 (BST) 在 Prolog 中实现此类结构的方法。

首先,我们需要一个 Prolog 术语中的搜索树表示形式。请记住,BST 是一棵二叉树,键值对存储在节点中。因此,我们可以将节点表示为谓词t(Key,Value,LeftChild,RightChild). 空树将由原子表示nil.

但我们不想直接对这种表示形式进行编程:通过提供适当的谓词,我们可以指定一个 抽象数据结构 (ADT)。这样做的原因是我们以后可以更改我们的实现以使用 平衡二叉树 或其他更复杂的数据结构。我们将此 ADT 称为有序映射,或ordmap. 以下是 ordmap 上的基本操作

  • empty_map(-Map): 与Map进行统一,使它成为空映射。
  • lookup_ordmap(+Key, +Map, -Value): 查找与Value相关联的KeyMap.
  • insert_ordmap(+Key, +Value, +Map0, -Map): 将对Key, Value插入映射Map0中,产生Map. 注意,之后,两者Map0Map都是有效的有序映射,它们最多只有一个元素不同。如果Key已经存在于Map0.
  • update_ordmap(+Key, +Value, +Map0, -Map): 与insert_ordmap类似,但会删除与Key相关联的任何先前值(并始终成功)。
  • remove_ordmap(+Key, +Map0, -Map, -Value): 从Key中删除Map0中,产生Map. 与Key相关联的值将返回到Value中。如果Key不在Map0.
  • member_ordmap(+Map, -Key, -Value): 按键顺序回溯Map中的所有键值对。
  • rmember_ordmap(+Map, -Key, -Value): 按键顺序回溯Map按键的逆序回溯。
  • size_ordmap(+Map, -Size): 确定Map.

中元素的数量。出于稍后会变得清晰的原因,我们有序映射中的键应始终是接地项。

实现empty_map很简单

empty_map(nil).

我们的下一个谓词lookup_map, 遵循 BST 操作的常用递归模式

lookup_ordmap(K, t(X,Y,L,R), V) :-
    (K == X ->
        V = Y
    ; K @< X ->
        lookup_ordmap(K,L,V)
    ;
        lookup_ordmap(K,R,V)
    ).

注意使用==/2而不是统一。这样做的原因在于使用了@</2,它根据术语的标准顺序比较术语。在此排序中,对于任意两个不同的术语 ,要么 == @< ,或 @< . 例如,a @< b, X @< YX @< foo. 事实上,自由变量始终是@<接地项。但是,当两个变量,或一个变量和一个接地项被统一时,排序会发生变化:在X=Y, X==Y之后也是真的。这就是为什么键原则上始终应该是接地项的原因:这样,排序始终保持(但我们将相应的检查留给我们有序映射数据结构的用户)。请注意,对值没有这种限制,因为它们不需要排序。

还要注意,我们没有针对nil的情况,因为在空树中查找任何东西始终会失败。

从二叉搜索树中删除元素 的实现可能有点棘手;以下代码使用其中序前驱替换具有两个子节点的节点,该前驱是左子树中的最大元素。它使用rm_max辅助谓词从子树中删除最大元素。

remove_ordmap(K, t(X,Y,L0,R), t(X,Y,L,R), V) :-
    K @< X,
    remove_ordmap(K,L0,L,V).
remove_ordmap(K, t(X,Y,L,R0), t(X,Y,L,R), V) :-
    K @> X,
    remove_ordmap(K,R0,R,V).
remove_ordmap(K, t(X,V,L,R), T, V) :-
    K == X,
    (L == nil ->
        T = R
    ; R == nil ->
        T = L
    ;
        rm_max(L,L1,K1,V1),
        T = t(K1,V1,L1,R)
    ).
rm_max(t(K,V,L,nil), L, K, V) :- !.
rm_max(t(X,Y,L,R0), t(X,Y,L,R), K, V) :-
    rm_max(R0,R,K,V).

现在很容易编写其余谓词

insert_ordmap(K, V, nil, t(K,V,nil,nil)).
insert_ordmap(K, V, t(X,Y,L0,R), t(X,Y,L,R)) :-
    K @< X,
    insert_ordmap(K,V,L0,L).
insert_ordmap(K, V, t(X,Y,L,R0), t(X,Y,L,R)) :-
    K @> X,
    insert_ordmap(K,V,R0,R).

member_ordmap(t(X,Y,L,R), K, V) :-
    member_ordmap(L,K,V) ;
    (X=K, Y=V) ;
    member_ordmap(R,K,V).

size_ordmap(nil, 0).
size_ordmap(t(_,_,L,R), N) :-
    size_ordmap(L,NL),
    size_ordmap(R,NR),
    N is NL+NR+1.

练习:实现rmember_ordmapupdate_ordmap.

关联映射数据结构内置在各种 Prolog 实现的库中(尽管通常方式不兼容)

这两个库都基于 AVL 树;请注意,SICStus 还提供了一个 library(assoc),但它是基于简单列表的。

华夏公益教科书