Algorithm Search⌘K

Proofs: Derivative-based regex matching

These are the human proofs of Derivative-based regex matching, at full length.

Derivative-based matching decides membership full

This proof shows: correctness, termination.

Write ν(x)\nu(x) for the NULLABLE bit of a node xx, and a−1L={v:av∈L}a^{-1}L = \{v : av \in L\} for the left quotient of a language LL by a symbol aa. For a word uu, u−1L={v:uv∈L}u^{-1}L = \{v : uv \in L\}. Then ε−1L=L\varepsilon^{-1}L = L, (ua)−1L=a−1(u−1L)(ua)^{-1}L = a^{-1}(u^{-1}L), and u∈Lu \in L if and only if ε∈u−1L\varepsilon \in u^{-1}L.

Lemma 1 (constructors). For all expressions x,y,x1,…,xjx, y, x_1, \ldots, x_j: L(CAT(x,y))=L(x)L(y)L(\mathrm{CAT}(x, y)) = L(x) L(y), L(ALT(x1,…,xj))=L(x1)∪⋯∪L(xj)L(\mathrm{ALT}(x_1, \ldots, x_j)) = L(x_1) \cup \cdots \cup L(x_j) and L(STAR(x))=L(x)∗L(\mathrm{STAR}(x)) = L(x)^*. The bit ν\nu stored with each node it returns is true if and only if ε\varepsilon is in its language.

Proof. Each case of lines L10 to L12 uses a language identity. For CAT: ∅L=L∅=∅\emptyset L = L \emptyset = \emptyset and {ε}L=L{ε}=L\{\varepsilon\} L = L \{\varepsilon\} = L. For ALT: union is associative, commutative and idempotent, so the union of the L(xi)L(x_i) equals the union of the languages of the members of the flattened set; L(∅)=∅L(\emptyset) = \emptyset adds nothing; an empty union is ∅\emptyset, and a union of one member is that member. For STAR: ∅∗={ε}∗={ε}\emptyset^* = \{\varepsilon\}^* = \{\varepsilon\}, and (L∗)∗=L∗(L^*)^* = L^*. When a new node is made, its bit is ν(x)∧ν(y)\nu(x) \land \nu(y) for a concatenation, the disjunction of the members' bits for a union, and true for a star. By induction on the order in which nodes are made, each bit is correct: ε∈L(x)L(y)\varepsilon \in L(x) L(y) if and only if ε∈L(x)\varepsilon \in L(x) and ε∈L(y)\varepsilon \in L(y); ε\varepsilon is in a union if and only if it is in a member; and ε∈L∗\varepsilon \in L^* always. The base nodes ∅\emptyset, ε\varepsilon and the symbols have the bits false, true and false. Hash-consing returns an existing node only when the node is equal, with the same language and bit. □\square

Lemma 2 (derivative). For every node rr and symbol aa, L(D(r,a))=a−1L(r)L(D(r, a)) = a^{-1} L(r).

Proof. By induction on the structure of rr. Memoization returns a value computed earlier for the same pair, so it does not change the result.

  • r=∅r = \emptyset: a−1∅=∅a^{-1}\emptyset = \emptyset. r=εr = \varepsilon: no word avav equals ε\varepsilon, so a−1{ε}=∅a^{-1}\{\varepsilon\} = \emptyset. This is line L5.
  • r=br = b, a symbol: a−1{b}a^{-1}\{b\} is {ε}\{\varepsilon\} if b=ab = a and ∅\emptyset otherwise. This is line L6.
  • r=s1∣⋯∣sjr = s_1 \mid \cdots \mid s_j: a−1(L1∪⋯∪Lj)=a−1L1∪⋯∪a−1Lja^{-1}(L_1 \cup \cdots \cup L_j) = a^{-1}L_1 \cup \cdots \cup a^{-1}L_j. By induction and Lemma 1, line L7 returns an expression for this set.
  • r=str = st: let av∈L(s)L(t)av \in L(s)L(t), so av=xyav = xy with x∈L(s)x \in L(s) and y∈L(t)y \in L(t). If x≠εx \ne \varepsilon, then x=ax′x = ax' with x′∈a−1L(s)x' \in a^{-1}L(s), and v=x′y∈(a−1L(s))L(t)v = x'y \in (a^{-1}L(s)) L(t). If x=εx = \varepsilon, then ε∈L(s)\varepsilon \in L(s) and v∈a−1L(t)v \in a^{-1}L(t). Conversely, both cases give av∈L(s)L(t)av \in L(s) L(t). So
a−1(L(s)L(t))=(a−1L(s)) L(t)  ∪  (a−1L(t) if ε∈L(s), else ∅).a^{-1}(L(s)L(t)) = (a^{-1}L(s))\, L(t) \;\cup\; \big(a^{-1}L(t) \text{ if } \varepsilon \in L(s), \text{ else } \emptyset\big).

By Lemma 1, ν(s)\nu(s) tells which case applies, and line L8 returns an expression for this set, by induction and Lemma 1.

  • r=s∗r = s^*: let av∈L(s)∗av \in L(s)^*. Then av=x1⋯xlav = x_1 \cdots x_l with l≥1l \ge 1 and each xi∈L(s)x_i \in L(s); drop the empty factors, so that x1x_1 is not empty. Then x1=ax′x_1 = ax' with x′∈a−1L(s)x' \in a^{-1}L(s), and v=x′x2⋯xl∈(a−1L(s))L(s)∗v = x' x_2 \cdots x_l \in (a^{-1}L(s)) L(s)^*. Conversely, if v=x′yv = x' y with ax′∈L(s)ax' \in L(s) and y∈L(s)∗y \in L(s)^*, then av=(ax′)y∈L(s)∗av = (ax') y \in L(s)^*. So a−1(L(s)∗)=(a−1L(s)) L(s)∗a^{-1}(L(s)^*) = (a^{-1}L(s))\, L(s)^*, which is what line L9 returns, by induction and Lemma 1, since the node rr itself has language L(s)∗L(s)^*. □\square

Termination. A call D(r,a)D(r, a) evaluates D only on (r,a)(r, a) and on pairs (z,a)(z, a) for children zz of rr. The children of a node were made before it, so they are strictly below it in the finite DAG of nodes reachable from rr, and the evaluation is a postorder over this DAG, with each pair evaluated at most once by memoization. Each step makes finitely many calls of the constructors, each of which does finitely many set and table operations. So each call of D stops. The loop of lines L1 to L3 runs at most nn times. So DERIV-MATCH stops.

Theorem. DERIV-MATCH(r0,w)(r_0, w) returns true if and only if w∈L(r0)w \in L(r_0).

Proof. Let rir_i be the value of rr after ii rounds of the loop. By induction on ii, L(ri)=(a1⋯ai)−1L(r0)L(r_i) = (a_1 \cdots a_i)^{-1} L(r_0). For i=0i = 0 this is ε−1L(r0)=L(r0)\varepsilon^{-1}L(r_0) = L(r_0). For i≥1i \ge 1, by Lemma 2,

L(ri)=ai−1L(ri−1)=ai−1((a1⋯ai−1)−1L(r0))=(a1⋯ai)−1L(r0).L(r_i) = a_i^{-1} L(r_{i-1}) = a_i^{-1}\big((a_1 \cdots a_{i-1})^{-1} L(r_0)\big) = (a_1 \cdots a_i)^{-1} L(r_0).

If line L3 returns false in round ii, then ri=∅r_i = \emptyset and (a1⋯ai)−1L(r0)=∅(a_1 \cdots a_i)^{-1}L(r_0) = \emptyset. Every word ww with prefix a1⋯aia_1 \cdots a_i has w=a1⋯aivw = a_1 \cdots a_i v, and w∈L(r0)w \in L(r_0) would put vv in this empty set. So w∉L(r0)w \notin L(r_0), and false is correct.

Otherwise the loop ends with r=rnr = r_n, and line L4 returns ν(rn)\nu(r_n). By Lemma 1, this is true if and only if ε∈L(rn)=w−1L(r0)\varepsilon \in L(r_n) = w^{-1} L(r_0), which holds if and only if w∈L(r0)w \in L(r_0). ■\blacksquare

Back to Derivative-based regex matching