pub(crate) const NONE: u32 = u32::MAX;
#[derive(Clone, Copy, Debug, PartialEq)]
struct Node {
base: u32,
parent: u32,
token: u32,
}
const FREE: Node = Node {
base: 0,
parent: NONE,
token: NONE,
};
const MAX_FAILURES: u8 = 16;
#[derive(Default)]
struct Candidates {
next: Vec<usize>,
}
impl Candidates {
fn find(&mut self, mut i: usize) -> usize {
while i < self.next.len() && self.next[i] != i {
let up = self.next[i];
if up < self.next.len() {
self.next[i] = self.next[up];
}
i = up;
}
i
}
fn remove(&mut self, i: usize) {
if i >= self.next.len() {
let len = self.next.len();
self.next.extend(len..=i);
}
self.next[i] = i + 1;
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct DoubleArrayTrie {
nodes: Vec<Node>,
}
impl DoubleArrayTrie {
pub(crate) fn build(mut pieces: Vec<(&[u8], u32)>) -> DoubleArrayTrie {
pieces.retain(|(key, _)| !key.is_empty());
pieces.sort_unstable();
pieces.dedup_by(|a, b| a.0 == b.0);
let mut nodes = vec![Node { parent: NONE, ..FREE }];
let mut used = vec![true];
let mut candidates = Candidates::default();
candidates.remove(0);
let mut failures: Vec<u8> = Vec::new();
let mut queue = std::collections::VecDeque::from([(0u32, 0usize, 0usize, pieces.len())]);
let mut children: Vec<(u8, usize, usize)> = Vec::new();
while let Some((node, depth, mut lo, hi)) = queue.pop_front() {
if lo < hi && pieces[lo].0.len() == depth {
nodes[node as usize].token = pieces[lo].1;
lo += 1;
}
children.clear();
while lo < hi {
let byte = pieces[lo].0[depth];
let mut end = lo + 1;
while end < hi && pieces[end].0[depth] == byte {
end += 1;
}
children.push((byte, lo, end));
lo = end;
}
if children.is_empty() {
continue;
}
let min_byte = children[0].0 as usize;
let mut slot = candidates.find(min_byte + 1);
let base = loop {
let base = slot - min_byte;
if children
.iter()
.all(|&(b, _, _)| !used.get(base + b as usize).copied().unwrap_or(false))
{
break base;
}
if slot >= failures.len() {
failures.resize(slot + 1, 0);
}
failures[slot] += 1;
if failures[slot] >= MAX_FAILURES {
candidates.remove(slot);
}
slot = candidates.find(slot + 1);
};
let top = base + children.last().unwrap().0 as usize;
if top >= nodes.len() {
nodes.resize(top + 1, FREE);
used.resize(top + 1, false);
}
nodes[node as usize].base = base as u32;
for &(b, lo, hi) in &children {
let slot = base + b as usize;
used[slot] = true;
candidates.remove(slot);
nodes[slot].parent = node;
queue.push_back((slot as u32, depth + 1, lo, hi));
}
}
assert!(nodes.len() < NONE as usize, "vocabulary trie exceeds 32-bit offsets");
nodes.resize(nodes.len() + 256, FREE);
DoubleArrayTrie { nodes }
}
#[inline(always)]
pub(crate) fn child(&self, node: u32, byte: u8) -> u32 {
let slot = self.nodes[node as usize].base as usize + byte as usize;
if self.nodes[slot].parent == node {
slot as u32
} else {
NONE
}
}
#[inline(always)]
pub(crate) fn token(&self, node: u32) -> u32 {
self.nodes[node as usize].token
}
pub(crate) fn walk(&self, key: &[u8]) -> u32 {
let mut node = 0;
for &byte in key {
node = self.child(node, byte);
if node == NONE {
return NONE;
}
}
node
}
pub(crate) fn len(&self) -> usize {
self.nodes.len()
}
pub(crate) fn get(&self, key: &[u8]) -> u32 {
match self.walk(key) {
NONE => NONE,
node => self.token(node),
}
}
#[inline(always)]
pub(crate) fn longest_prefix(&self, start: u32, bytes: &[u8]) -> Option<(u32, usize)> {
let mut node = start;
let mut best = None;
for (i, &byte) in bytes.iter().enumerate() {
node = self.child(node, byte);
if node == NONE {
break;
}
let token = self.token(node);
if token != NONE {
best = Some((token, i + 1));
}
}
best
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_a_map() {
let words = ["a", "ab", "abc", "b", "##b", "##bc", "héllo", "\u{ff}", "zz", "z"];
let pieces: Vec<(&[u8], u32)> = words
.iter()
.enumerate()
.map(|(i, w)| (w.as_bytes(), i as u32))
.collect();
let trie = DoubleArrayTrie::build(pieces);
for (i, w) in words.iter().enumerate() {
assert_eq!(trie.get(w.as_bytes()), i as u32, "{w}");
}
for missing in ["", "abcd", "c", "##", "#", "hé", "zzz"] {
assert_eq!(trie.get(missing.as_bytes()), NONE, "{missing}");
}
assert_eq!(trie.longest_prefix(0, b"abd"), Some((1, 2)));
assert_eq!(trie.longest_prefix(0, b"c"), None);
let cont = trie.walk(b"##");
assert_eq!(trie.longest_prefix(cont, b"bcd"), Some((5, 2)));
assert_eq!(trie.longest_prefix(cont, b""), None);
}
#[test]
fn duplicate_keys_keep_the_smallest_id() {
let trie = DoubleArrayTrie::build(vec![(b"ab".as_slice(), 7), (b"ab", 3), (b"a", 1), (b"ab", 5)]);
assert_eq!(trie.get(b"ab"), 3);
assert_eq!(trie.get(b"a"), 1);
}
}