Algorithm Search⌘K

Proofs: HAT-trie

These are the human proofs of HAT-trie, at full length.

The HAT-trie stores each key once and finds it, and bursts and splits keep its invariant full

This proof shows: correctness.

We use the pseudocode, the notation and the invariant (H1) to (H4) of the page. For a node xx, write u(x)u(x) for its string, the symbols of the pointers from the root to xx. An entry stands for the key given by (H2).

Lemma 1 (descent). For a string ss, DESCEND stops at the node xx with u(x)=s[0…d−1]u(x) = s[0 \ldots d - 1], where either d=∣s∣d = \lvert s \rvert or the pointer x.child[s[d]]x.child[s[d]] leads to a container yy. A key ss can only be stored at x.valuex.value in the first case, and only in yy in the second, under the string s[d+1…]s[d + 1 \ldots] if yy is pure and s[d…]s[d \ldots] if yy is hybrid.

Proof. The loop follows one pointer per symbol while it leads to a trie node, so u(x)=s[0…d−1]u(x) = s[0 \ldots d - 1]. A key kk is stored at a node value when k=u(x)k = u(x), or in a container under a node x′x' whose string u(x′)u(x') is a prefix of kk, with the pointer by k[∣u(x′)∣]k[\lvert u(x') \rvert] leading to that container, by (H2). Suppose ss is stored. If s=u(x′)s = u(x') for a node x′x', the descent reaches x′x' and stops with d=∣s∣d = \lvert s \rvert, since it consumes the symbols of u(x′)u(x') along existing nodes; so x′=xx' = x. Otherwise the container holding ss hangs under a node x′x' by the pointer s[∣u(x′)∣]s[\lvert u(x') \rvert]; the descent passes through x′x' and stops there, because that pointer leads to a container; so x′=xx' = x and the container is yy, and the stored string is the one given by (H2). □\square

GET. By Lemma 1, GET looks at the only place where ss can be. By (H3) the entry for the string is in the slot of its hash, which FIND scans, and by (H4) it is there at most once. So GET returns the value of ss if it is a key, and NIL otherwise.

PUT. By Lemma 1 and the same argument, PUT updates the existing entry of ss if there is one, and else adds one entry, at the place where GET will look. So (H2) and (H4) hold, and (H3) holds except that yy may have L+1L + 1 entries.

BURST keeps the keys. Each case of lines L9 to L11 replaces one container yy under a node xx and moves its entries; we show that each entry keeps the key it stands for.

  • Line L9. yy is pure under symbol cc of xx, so its entry tt stands for u(x)ctu(x) c t. The new node zz has u(z)=u(x)cu(z) = u(x) c. The empty entry becomes z.valuez.value, standing for u(z)u(z). Each other entry tt goes into the hybrid container of zz, which covers all symbols, as the string tt, and stands for u(z)t=u(x)ctu(z) t = u(x) c t, with t[0]t[0] in its range.
  • Line L10. yy is hybrid under xx with range [lo,hi)[lo, hi), and its entries tt stand for u(x)tu(x) t. The new containers are hybrid under xx with ranges that partition [lo,hi)[lo, hi), each pointer of the range now leads to the part that contains it, and each entry goes to the part that contains t[0]t[0]. So tt still stands for u(x)tu(x) t and lies in the container its first symbol points to.
  • Line L11. yy is hybrid with range [c,c+1)[c, c + 1), so all its entries start with cc and stand for u(x)tu(x) t with t=ct′t = c t'. As a pure container under cc, the entry t′t' stands for u(x)ct′u(x) c t', the same key.

In each case the moved entries are distinct strings in the new container, because they stood for distinct keys, and each is put in the slot of its hash. So (H2) to (H4) hold except for the size bound of the new containers, which line L12 puts on the work list.

BURST ends. Assume L≥1L \ge 1. A container processed at line L9 is pure with more than L≥1L \ge 1 entries, so it has a nonempty entry tt, and the new node zz has u(z)=u(x)cu(z) = u(x) c, a proper prefix of the key u(x)ctu(x) c t. Node strings are distinct, so line L9 runs at most once for each proper prefix of a stored key, finitely often. Between two runs of line L9, the work list only processes containers under existing nodes. Line L10 replaces a container by parts with strictly smaller ranges, and line L11 turns a container with a range of one symbol into a pure one, which is not split again. For one node, the ranges of its hybrid containers partition its RR pointers, so at most R−1R - 1 splits and RR conversions can happen before the next node is created. Hence the work list empties, and at that point every container has at most LL entries, so (H3) holds. ■\blacksquare

Back to HAT-trie