跳轉到內容

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),但它是基於簡單列表的。

華夏公益教科書