Algorithm Search⌘K

Proofs: Hoffmann-O'Donnell tree pattern matching

These are the human proofs of Hoffmann-O'Donnell tree pattern matching, at full length.

The bottom-up matching automaton computes exactly the match sets 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), positions as sequences of positive integers, t∣pt|_p for the subterm at pp, and substitutions σ\sigma. The patterns are p1,…,pFp_1, \ldots, p_F. For a term ll, lin(l)lin(l) is ll with each variable occurrence replaced by a new variable, all distinct. A pattern ll matches uu if lσ=ul\sigma = u for some σ\sigma; variables of the subject are treated as constants.

The pattern forest. Write vv for one wildcard symbol. For a term ll, let lˉ\bar l be ll with every variable replaced by vv. Line L1 builds PFPF, the set of all qˉ\bar q for subterms qq of the patterns; it contains vv when some pattern has a variable, and the code always adds vv (id 0). Say that a member ww of PFPF fits a term uu if w=vw = v, or w=f(w1,…,wk)w = f(w_1, \ldots, w_k), u=f(u1,…,uk)u = f(u_1, \ldots, u_k) with the same ff and kk, and each wiw_i fits uiu_i.

Lemma 1. For a term ll and a subject term uu, lˉ\bar l fits uu if and only if lin(l)lin(l) matches uu.

Proof. Induction on ll. If ll is a variable, lˉ=v\bar l = v fits every uu, and lin(l)lin(l) is a variable, which matches every uu. If l=f(l1,…,lk)l = f(l_1, \ldots, l_k), then lin(l)=f(lin(l1),…,lin(lk))lin(l) = f(lin(l_1), \ldots, lin(l_k)) up to renaming, with the variables of the arguments pairwise disjoint. It matches uu iff u=f(u1,…,uk)u = f(u_1, \ldots, u_k) and each lin(li)lin(l_i) matches uiu_i, because the matchers of the arguments have disjoint domains and combine into one substitution. By the induction hypothesis this holds iff each lˉi\bar l_i fits uiu_i, that is, iff lˉ\bar l fits uu. ■\blacksquare

For a subject term uu, its match set is MS(u)={w∈PF:w fits u}MS(u) = \{ w \in PF : w \text{ fits } u \}.

Lemma 2 (one step). For u=f(u1,…,uk)u = f(u_1, \ldots, u_k), MS(u)={v}∪{f(w1,…,wk)∈PF:wi∈MS(ui) for all i}MS(u) = \{v\} \cup \{ f(w_1, \ldots, w_k) \in PF : w_i \in MS(u_i) \text{ for all } i \}. For a subject variable or a symbol f/kf/k that no member of PFPF has at the root, MS(u)={v}MS(u) = \{v\}.

Proof. This is the definition of "fits", restricted to the members of PFPF. A member other than vv fits uu only if its root is the root symbol of uu with the same arity, and a subject variable is not the root of any member. ■\blacksquare

The function δf(M1,…,Mk)={v}∪{f(w1,…,wk)∈PF:wi∈Mi}\delta_f(M_1, \ldots, M_k) = \{v\} \cup \{ f(w_1, \ldots, w_k) \in PF : w_i \in M_i \} is what line L5 computes.

Lemma 3 (preprocessing). The loop of lines L3 to L6 stops. When it stops, the set SS of states contains {v}\{v\} and is closed under every δf\delta_f: for every symbol f/kf/k at the root of a member of PFPF and all M1,…,Mk∈SM_1, \ldots, M_k \in S, the table entry Tf[M1,…,Mk]T_f[M_1, \ldots, M_k] is defined and equals δf(M1,…,Mk)∈S\delta_f(M_1, \ldots, M_k) \in S.

Proof. Every state is a subset of PFPF, and SS has no repeated sets, so ∣S∣≤2∣PF∣|S| \le 2^{|PF|}. Each round either adds an entry to a table or makes no change. The number of possible entries is at most ∑f∣S∣kf\sum_f |S|^{k_f}, which is bounded, so the loop stops after finitely many rounds. In the last round no tuple of states lacks an entry, for every symbol, so every entry is defined; each entry was set to δf\delta_f of its tuple and added to SS (line L6). The state {v}\{v\} is added before the loop. ■\blacksquare

Theorem. For every subject tt and every position pp of tt, lines L8 to L10 give pp the state MS(t∣p)MS(t|_p). Lines L11 and L12 report the pair (p,j)(p, j) if and only if pjp_j matches t∣pt|_p.

Proof. The loop of line L8 handles a node after all its children (a node is pushed again below its children, and handled when it is popped the second time, or at once if it is a leaf). By induction on the height of t∣pt|_p:

  • If t∣pt|_p is a variable, or its symbol f/kf/k has no table, line L9 or line L10 gives {v}\{v\}, which is MS(t∣p)MS(t|_p) by Lemma 2.
  • Else t∣p=f(u1,…,uk)t|_p = f(u_1, \ldots, u_k), and its children have the states MS(ui)∈SMS(u_i) \in S by the induction hypothesis. By Lemma 3 the entry Tf[MS(u1),…,MS(uk)]T_f[MS(u_1), \ldots, MS(u_k)] is defined and equals δf(MS(u1),…,MS(uk))\delta_f(MS(u_1), \ldots, MS(u_k)), which is MS(t∣p)MS(t|_p) by Lemma 2. A constant is the case k=0k = 0.

Line L7 lists, for each state MM, the patterns jj with pˉj∈M\bar p_j \in M. So line L11 offers jj at pp iff pˉj\bar p_j fits t∣pt|_p, iff lin(pj)lin(p_j) matches t∣pt|_p (Lemma 1). If pjp_j is linear, lin(pj)lin(p_j) is pjp_j up to renaming, and the pair is reported. If not, line L12 reports it iff the full matcher finds σ\sigma with pjσ=t∣pp_j\sigma = t|_p. In both cases the pair is reported iff pjp_j matches t∣pt|_p: one direction is the check itself, and if pjp_j matches t∣pt|_p then lin(pj)lin(p_j) matches it too, so line L11 offers it. Each pair (p,j)(p, j) is considered once, because each node has one state and each state lists jj at most once. ■\blacksquare

Remark. Lemma 3 does not need the closure to be computed by rounds. Any order that sets TfT_f on every tuple of reachable states gives the same states and tables, because δf\delta_f depends only on its arguments. The reachable states are exactly the match sets of ground terms over the symbols, together with {v}\{v\}, by induction on the round in which a state is first added.

Back to Hoffmann-O'Donnell tree pattern matching