Algorithm Search⌘K

Proofs: Discrimination tree

These are the human proofs of Discrimination tree, at full length.

Discrimination tree retrieval returns exactly the patterns whose linear form matches the query full

This proof shows: correctness.

We use the pseudocode of the page and the notation of the term rewriting pages: a signature Σ\Sigma, variables VV, terms T(Σ,V)T(\Sigma, V), and substitutions σ\sigma. A function symbol ff of arity kk is written f/kf/k when it is a symbol of a key. For a pattern ll, key(l)key(l) is its preorder sequence of symbols with each variable replaced by ∗*. For a term ll, lin(l)lin(l) is ll with each occurrence of a variable replaced by a new variable, all distinct. A term pp matches tt if pσ=tp\sigma = t for some σ\sigma; the variables of tt are treated as constants.

For a node xx of the tree, str(x)str(x) is the sequence of edge symbols on the path from the root to xx. The edges out of a node carry distinct symbols, because x.nextx.next is a map. The tree stores the entry (l,v)(l, v) at a node xx with str(x)=key(l)str(x) = key(l).

Invariant. (D1) An entry (l,v)(l, v) is in the set if and only if it is in x.valsx.vals for the node xx with str(x)=key(l)str(x) = key(l). (D2) Each node other than the root has an entry or a child. (D3) D.nD.n is the number of entries.

Insert and delete keep the invariant

INSERT walks from the root along key(l)key(l), creates each missing node as the only child for its symbol (line L3), and adds the entry at the last node xx, which has str(x)=key(l)str(x) = key(l) (line L5). Other nodes keep their entries, so (D1) holds. Each new node is on the path to xx, so it has a child on that path or it is xx, which has an entry, and (D2) holds.

DELETE walks along key(l)key(l) (lines L18 to L20). If an edge is missing, no node has str=key(l)str = key(l), so by (D1) the entry is not in the set, and DELETE returns false without a change. Else it reaches the node xx with str(x)=key(l)str(x) = key(l), and by (D1) the entry is in the set iff it is in x.valsx.vals; line L21 removes one copy, or returns false. Lines L22 to L24 then go up from xx and remove each node that has no entry and no child, and stop at the first node that has one. A removed node has no entry, so (D1) still holds. A node that stays either was not changed, or is the node where the loop stopped, which has an entry or a child, so (D2) holds. (D3) is kept by the counter. ■\blacksquare

Retrieval

Let tt be the query, with preorder symbols a0,…,an−1a_0, \ldots, a_{n-1} (line L6). Each aia_i starts one subterm tit_i of tt, and the subterm ends just before skip[i]skip[i]; a variable yy of tt gives the symbol ?y?y, which is not ∗* and not a function symbol. If ai=f/ka_i = f/k, then ti=f(u1,…,uk)t_i = f(u_1, \ldots, u_k), u1u_1 starts at i+1i + 1, and uj+1u_{j+1} starts at the end of uju_j, the last one ending at skip[i]skip[i]. This is the definition of preorder.

Walk. For a sequence ww of key symbols and an index i≤ni \le n, define the walk of ww from ii: start at ii; for each symbol cc of ww in turn, if i<ni < n and c=∗c = *, set i←skip[i]i \leftarrow skip[i]; if i<ni < n and c=aic = a_i is a function symbol, set i←i+1i \leftarrow i + 1; else the walk fails. The walk is deterministic, so it fails or ends at one index.

Lemma 1 (forest). Let l1,…,lml_1, \ldots, l_m be terms, and let w=key(l1)⋯key(lm)w = key(l_1) \cdots key(l_m). Let i0=ii_0 = i be an index. The walk of ww from ii ends at jj if and only if there are indices i0,i1,…,imi_0, i_1, \ldots, i_m with ir=skip[ir−1]i_{r} = skip[i_{r-1}] and ir≤ni_r \le n for r=1,…,mr = 1, \ldots, m, such that lin(lr)lin(l_r) matches tir−1t_{i_{r-1}} for each rr, and j=imj = i_m. (The term tir−1t_{i_{r-1}} must exist, so ir−1<ni_{r-1} < n, and im≤skip[im−1]i_m \le skip[i_{m-1}] is part of the claim.)

Proof. Induction on the total size ∣l1∣+⋯+∣lm∣|l_1| + \cdots + |l_m|. If m=0m = 0, ww is empty and the walk ends at ii; take j=i0j = i_0. Let m≥1m \ge 1.

  • If l1l_1 is a variable, key(l1)=∗key(l_1) = *. The walk does its first step iff i<ni < n, and then it moves to skip[i]skip[i]. The term lin(l1)lin(l_1) is a variable, which matches tit_i. The claim follows from the induction hypothesis for l2,…,lml_2, \ldots, l_m from skip[i]skip[i].
  • If l1=f(s1,…,sk)l_1 = f(s_1, \ldots, s_k), key(l1)=f/k key(s1)⋯key(sk)key(l_1) = f/k\ key(s_1) \cdots key(s_k). The first step succeeds iff i<ni < n and ai=f/ka_i = f/k, that is, ti=f(u1,…,uk)t_i = f(u_1, \ldots, u_k), and then the walk continues from i+1i + 1 with key(s1)⋯key(sk) key(l2)⋯key(lm)key(s_1) \cdots key(s_k)\ key(l_2) \cdots key(l_m), a forest of smaller total size. By the induction hypothesis, this walk ends at jj iff lin(s1),…,lin(sk)lin(s_1), \ldots, lin(s_k) match the consecutive subterms starting at i+1i + 1, which are u1,…,uku_1, \ldots, u_k and end at skip[i]skip[i], and then lin(l2),…,lin(lm)lin(l_2), \ldots, lin(l_m) match consecutive subterms from skip[i]skip[i], ending at jj. Because lin(l1)lin(l_1) has distinct variables, the variables of lin(s1),…,lin(sk)lin(s_1), \ldots, lin(s_k) are disjoint, so the matchers combine into one, and lin(l1)=f(lin(s1),…,lin(sk))lin(l_1) = f(lin(s_1), \ldots, lin(s_k)) up to renaming matches f(u1,…,uk)=tif(u_1, \ldots, u_k) = t_i iff each lin(sr)lin(s_r) matches uru_r. If aia_i is another function symbol or a query variable, the walk fails, and lin(l1)lin(l_1), whose root is ff, does not match tit_i, whose root is not f/kf/k. ■\blacksquare

Lemma 2 (search). The loop of lines L8 to L12 pops the pair (x,i)(x, i) if and only if the walk of str(x)str(x) from 00 ends at ii. Each node is popped at most once.

Proof. If: induction on the length of str(x)str(x). The root is pushed with 00 at line L7. Let xx be a child of yy by the symbol cc, and let the walk of str(x)str(x) end at ii. Then the walk of str(y)str(y) ends at some i′<ni' < n, so by the induction hypothesis (y,i′)(y, i') is popped, and i′≠ni' \ne n, so lines L11 and L12 run. If c=∗c = *, line L11 pushes (x,skip[i′])(x, skip[i']) and skip[i′]=iskip[i'] = i. If cc is a function symbol, then c=ai′c = a_{i'}, line L12 pushes (x,i′+1)(x, i' + 1) and i′+1=ii' + 1 = i. Every pushed pair is popped, since the loop runs until the stack is empty.

Only if: each push at lines L11 and L12 extends the walk by one step of its definition, so by induction on the number of pushes, every pushed (x,i)(x, i) has a walk of str(x)str(x) that ends at ii. The walk is deterministic, and each node has one parent, so each node is pushed at most once. ■\blacksquare

Theorem. RETRIEVE(D,t)(D, t) returns each entry (l,v)(l, v) of DD such that lin(l)lin(l) matches tt, once, and no other entry. MATCH(D,t)(D, t) returns each entry (l,v)(l, v) such that ll matches tt, once, with its matcher.

Proof. Line L10 outputs the entries of xx when (x,i)(x, i) is popped with i=ni = n. Let (l,v)(l, v) be an entry and xx its node, str(x)=key(l)str(x) = key(l) by (D1). By Lemma 2, (x,n)(x, n) is popped iff the walk of key(l)key(l) from 00 ends at nn. By Lemma 1 with m=1m = 1, this holds iff lin(l)lin(l) matches t0=tt_0 = t and skip[0]=nskip[0] = n, and skip[0]=nskip[0] = n is always true. Lemma 2 also says that xx is popped at most once, so the entry is output at most once.

For MATCH: if ll matches tt with lσ=tl\sigma = t, then lin(l)lin(l) matches tt (map each new variable to the image of its old variable), so the entry is a candidate, and line L16 accepts it. Line L16 rejects every candidate that ll does not match. ■\blacksquare

Remark. The converse of "ll matches tt implies lin(l)lin(l) matches tt" is false for non-linear ll: f(x,x)f(x, x) is a candidate for f(g(a),b)f(g(a), b), but it does not match. For linear ll, lin(l)lin(l) is ll up to renaming, so RETRIEVE alone is exact.

Back to Discrimination tree