trie: one node per rune, children in rune order, and the last path kept

The trie walked a key byte by byte, so a Hangul syllable cost three
nodes and every lookup first spelled its key back into UTF-8.  It walks
runes now: a third of the nodes for Korean data, and no buffer in the
lookup.  Children are kept in rune order, so both the search and the
insert stop early, and a trie remembers the path of the last key put,
which a file sorted by key — hanja.dict is one — walks straight down.
This commit is contained in:
2026-08-17 12:50:05 +09:00
parent 6a6749e824
commit 3527bcd489
3 changed files with 41 additions and 24 deletions

5
dat.h
View File

@@ -74,7 +74,7 @@ struct Tnode
int vlen; int vlen;
int child; int child;
int sibling; int sibling;
char c; Rune c;
}; };
typedef struct Trie Trie; typedef struct Trie Trie;
@@ -83,6 +83,9 @@ struct Trie
Tnode *nodes; Tnode *nodes;
int n; int n;
int cap; int cap;
/* the last key put and its path, so a sorted file walks it once */
Str last;
int path[Maxrunes];
}; };
enum enum

2
dict.c
View File

@@ -24,7 +24,7 @@ words(Tnode *nd, Str *out, int n, int max)
return n; return n;
} }
/* The entries at and below a node, in the dictionary's order. */ /* The entries at and below a node, the shortest keys first. */
static int static int
below(Trie *t, int ni, Str *out, int n, int max) below(Trie *t, int ni, Str *out, int n, int max)
{ {

58
trie.c
View File

@@ -3,9 +3,9 @@
#include "fn.h" #include "fn.h"
/* /*
* A byte trie over UTF-8 keys. Maps need prefix matches while composing; * A trie over the runes of a key. Maps need prefix matches while
* dictionaries need exact matches; both are small enough for one structure. * composing; dictionaries need exact matches; both are small enough for
* Node 0 is the root. * one structure. Node 0 is the root.
*/ */
static int static int
@@ -24,32 +24,37 @@ newnode(Trie *t)
return i; return i;
} }
/* The children of a node are kept in rune order, so both end early. */
static int static int
find(Trie *t, int ni, char c) find(Trie *t, int ni, Rune c)
{ {
int pi; int pi;
for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling) for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling){
if(t->nodes[pi].c == c) if(t->nodes[pi].c == c)
return pi; return pi;
if(t->nodes[pi].c > c)
break;
}
return -1; return -1;
} }
/* Appends, so that a walk of the children keeps the file's order. */
static int static int
add(Trie *t, int ni, char c) add(Trie *t, int ni, Rune c)
{ {
int last, pi; int next, pi, prev;
last = -1; prev = -1;
for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling) for(next = t->nodes[ni].child; next >= 0 && t->nodes[next].c < c;
last = pi; next = t->nodes[next].sibling)
prev = next;
pi = newnode(t); pi = newnode(t);
t->nodes[pi].c = c; t->nodes[pi].c = c;
if(last < 0) t->nodes[pi].sibling = next;
if(prev < 0)
t->nodes[ni].child = pi; t->nodes[ni].child = pi;
else else
t->nodes[last].sibling = pi; t->nodes[prev].sibling = pi;
return pi; return pi;
} }
@@ -69,15 +74,26 @@ trienew(void)
void void
trieput(Trie *t, char *key, int klen, char *val, int vlen) trieput(Trie *t, char *key, int klen, char *val, int vlen)
{ {
int ni, ci, i; Str k;
int ci, i, ni, same;
if(!sinit(&k, key, klen))
return;
ni = 0; ni = 0;
for(i = 0; i < klen; i++){ same = 1;
ci = find(t, ni, key[i]); for(i = 0; i < k.n; i++){
if(same && i < t->last.n && t->last.r[i] == k.r[i]){
ni = t->path[i];
continue;
}
same = 0;
ci = find(t, ni, k.r[i]);
if(ci < 0) if(ci < 0)
ci = add(t, ni, key[i]); ci = add(t, ni, k.r[i]);
ni = ci; ni = ci;
t->path[i] = ni;
} }
t->last = k;
free(t->nodes[ni].val); free(t->nodes[ni].val);
t->nodes[ni].val = emalloc(vlen + 1); t->nodes[ni].val = emalloc(vlen + 1);
memmove(t->nodes[ni].val, val, vlen); memmove(t->nodes[ni].val, val, vlen);
@@ -163,15 +179,13 @@ trieclose(Trie *t)
int int
trienode(Trie *t, Str *key) trienode(Trie *t, Str *key)
{ {
char buf[Maxutf]; int i, ni;
int i, klen, ni;
if(t == nil) if(t == nil)
return -1; return -1;
klen = stoutf(key, buf, sizeof buf);
ni = 0; ni = 0;
for(i = 0; i < klen && ni >= 0; i++) for(i = 0; i < key->n && ni >= 0; i++)
ni = find(t, ni, buf[i]); ni = find(t, ni, key->r[i]);
return ni; return ni;
} }