Ensure all dictionary backends support MappedDictionary (value storage) and ValuedDictZipper (hierarchical navigation with values) to provide complete layer support across the library.
Phase 6 is 100% complete! 🎉
As of 2025-11-11, comprehensive verification and analysis revealed:
All production-ready backends now support the complete feature set. OptimizedDawg was deprecated after benchmarking showed DynamicDawg provides superior performance (11× faster construction) with full feature support.
Last Updated: 2025-11-11
Implemented (9/10): 🎉
Deprecated/Skipped (2/10):
Implemented (7/7): 🎉 COMPLETE!
Not Applicable:
Current:
pub struct DoubleArrayTrie {
shared: DATShared,
// ...
}
Target:
pub struct DoubleArrayTrie<V: DictionaryValue = ()> {
shared: DATShared<V>,
// ...
}
Changes Required:
DoubleArrayTrieDATSharedDoubleArrayTrieBuilderDoubleArrayTrieNodeV = ()Current Structure:
struct DATShared {
base: Arc<Vec<i32>>,
check: Arc<Vec<i32>>,
is_final: Arc<Vec<bool>>,
edges: Arc<Vec<Vec<u8>>>,
}
Target Structure:
struct DATShared<V: DictionaryValue> {
base: Arc<Vec<i32>>,
check: Arc<Vec<i32>>,
is_final: Arc<Vec<bool>>,
edges: Arc<Vec<Vec<u8>>>,
values: Arc<Vec<Option<V>>>, // NEW: indexed by state
}
Design Decision:
Vec<Option<V>> indexed by state numberis_final arrayAdd Fields:
pub struct DoubleArrayTrieBuilder<V: DictionaryValue = ()> {
base: Vec<i32>,
check: Vec<i32>,
is_final: Vec<bool>,
values: Vec<Option<V>>, // NEW
// ... existing fields
}
Add Methods:
impl<V: DictionaryValue> DoubleArrayTrieBuilder<V> {
// Keep existing insert() for backward compatibility
pub fn insert(&mut self, term: &str) -> bool {
self.insert_with_value(term, None)
}
// NEW: Insert with optional value
pub fn insert_with_value(&mut self, term: &str, value: Option<V>) -> bool {
// Same logic as current insert()
// But also store value at final state
// ...
if is_new_term {
while state >= self.values.len() {
self.values.push(None);
}
self.values[state] = value;
true
} else {
false
}
}
}
Update build():
pub fn build(self) -> DoubleArrayTrie<V> {
// ... existing edge computation ...
DoubleArrayTrie {
shared: DATShared {
base: Arc::new(self.base),
check: Arc::new(self.check),
is_final: Arc::new(self.is_final),
edges: Arc::new(edges),
values: Arc::new(self.values), // NEW
},
// ...
}
}
impl<V: DictionaryValue> DoubleArrayTrie<V> {
/// Create a DAT from an iterator of (term, value) pairs.
pub fn from_terms_with_values<I, S>(terms: I) -> Self
where
I: IntoIterator<Item = (S, V)>,
S: AsRef<str>,
{
let mut term_value_pairs: Vec<(String, V)> = terms
.into_iter()
.map(|(s, v)| (s.as_ref().to_string(), v))
.collect();
// Sort by term
term_value_pairs.sort_by(|a, b| a.0.cmp(&b.0));
// Remove duplicates (keep last value)
term_value_pairs.dedup_by(|a, b| {
if a.0 == b.0 {
b.1 = a.1.clone(); // Keep most recent value
true
} else {
false
}
});
let mut builder = DoubleArrayTrieBuilder::new();
for (term, value) in term_value_pairs {
builder.insert_with_value(&term, Some(value));
}
builder.build()
}
}
impl<V: DictionaryValue> MappedDictionary for DoubleArrayTrie<V> {
type Value = V;
fn get_value(&self, term: &str) -> Option<Self::Value> {
// Navigate to final state
let mut state = 1; // Root
for &byte in term.as_bytes() {
let base = self.shared.base[state];
if base < 0 {
return None;
}
let next = (base as usize) + (byte as usize);
if next >= self.shared.check.len()
|| self.shared.check[next] != state as i32 {
return None;
}
state = next;
}
// Check if final and return value
if state < self.shared.is_final.len() && self.shared.is_final[state] {
self.shared.values.get(state).and_then(|v| v.clone())
} else {
None
}
}
fn contains_with_value<F>(&self, term: &str, predicate: F) -> bool
where
F: Fn(&Self::Value) -> bool,
{
match self.get_value(term) {
Some(ref value) => predicate(value),
None => false,
}
}
}
impl<V: DictionaryValue> MappedDictionaryNode for DoubleArrayTrieNode<V> {
type Value = V;
fn value(&self) -> Option<Self::Value> {
if self.state < self.shared.values.len() {
self.shared.values[self.state].clone()
} else {
None
}
}
}
Ensure values field is included in serialization:
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
struct DATShared<V: DictionaryValue> {
#[cfg_attr(
feature = "serialization",
serde(
serialize_with = "serialize_arc_vec",
deserialize_with = "deserialize_arc_vec"
)
)]
values: Arc<Vec<Option<V>>>,
// ... other fields
}
File: src/dictionary/double_array_trie_zipper.rs
use crate::dictionary::double_array_trie::{DoubleArrayTrie, DATShared};
use crate::dictionary::value::DictionaryValue;
use crate::dictionary::zipper::{DictZipper, ValuedDictZipper};
use std::sync::Arc;
/// Zipper for navigating DoubleArrayTrie structures.
#[derive(Clone, Debug)]
pub struct DoubleArrayTrieZipper<V: DictionaryValue = ()> {
/// Current state index
state: usize,
/// Shared DAT data
shared: Arc<DATShared<V>>,
}
impl<V: DictionaryValue> DoubleArrayTrieZipper<V> {
/// Create a new zipper at the root of the dictionary.
pub fn new_from_dict(dict: &DoubleArrayTrie<V>) -> Self {
Self {
state: 1, // Root is state 1
shared: Arc::clone(&dict.shared),
}
}
/// Get the current state index.
pub fn state(&self) -> usize {
self.state
}
}
impl<V: DictionaryValue> DictZipper for DoubleArrayTrieZipper<V> {
type Unit = u8;
fn is_final(&self) -> bool {
self.state < self.shared.is_final.len()
&& self.shared.is_final[self.state]
}
fn descend(&self, label: Self::Unit) -> Option<Self> {
if self.state >= self.shared.base.len() {
return None;
}
let base = self.shared.base[self.state];
if base < 0 {
return None;
}
let next = (base as usize) + (label as usize);
if next >= self.shared.check.len()
|| self.shared.check[next] != self.state as i32 {
return None;
}
Some(Self {
state: next,
shared: Arc::clone(&self.shared),
})
}
fn children(&self) -> impl Iterator<Item = (Self::Unit, Self)> + '_ {
// Use precomputed edge list for efficiency
let edges = if self.state < self.shared.edges.len() {
&self.shared.edges[self.state]
} else {
&[]
};
edges.iter().filter_map(move |&byte| {
self.descend(byte).map(|child| (byte, child))
})
}
}
impl<V: DictionaryValue> ValuedDictZipper for DoubleArrayTrieZipper<V> {
type Value = V;
fn value(&self) -> Option<Self::Value> {
if self.is_final() && self.state < self.shared.values.len() {
self.shared.values[self.state].clone()
} else {
None
}
}
}
In src/dictionary/mod.rs:
pub mod double_array_trie_zipper;
pub use double_array_trie_zipper::DoubleArrayTrieZipper;
#[cfg(test)]
mod tests {
use super::*;
use crate::dictionary::{MappedDictionary, MutableMappedDictionary};
#[test]
fn test_double_array_trie_with_values() {
let terms = vec![
("apple", 1),
("application", 2),
("apply", 3),
];
let dict = DoubleArrayTrie::from_terms_with_values(terms);
assert_eq!(dict.get_value("apple"), Some(1));
assert_eq!(dict.get_value("application"), Some(2));
assert_eq!(dict.get_value("apply"), Some(3));
assert_eq!(dict.get_value("apricot"), None);
}
#[test]
fn test_contains_with_value() {
let dict = DoubleArrayTrie::from_terms_with_values(vec![
("test", 42),
("testing", 100),
]);
assert!(dict.contains_with_value("test", |v| *v == 42));
assert!(dict.contains_with_value("testing", |v| *v > 50));
assert!(!dict.contains_with_value("test", |v| *v > 50));
}
#[test]
fn test_zipper_with_values() {
use crate::dictionary::zipper::{DictZipper, ValuedDictZipper};
let dict = DoubleArrayTrie::from_terms_with_values(vec![
("cat", 1),
("catch", 2),
]);
let zipper = DoubleArrayTrieZipper::new_from_dict(&dict);
// Navigate to "cat"
let z = zipper.descend(b'c')
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b't'))
.unwrap();
assert!(z.is_final());
assert_eq!(z.value(), Some(1));
// Continue to "catch"
let z = z.descend(b'c')
.and_then(|z| z.descend(b'h'))
.unwrap();
assert!(z.is_final());
assert_eq!(z.value(), Some(2));
}
#[test]
fn test_backward_compatibility() {
// Default type parameter should be ()
let dict: DoubleArrayTrie = DoubleArrayTrie::from_terms(vec!["test", "testing"]);
assert!(dict.contains("test"));
assert_eq!(dict.len(), Some(2));
}
}
Total estimate: 16-20 hours over 2-3 weeks
V = () defaultPhase 6 is complete! All production backends now support full feature sets (MappedDictionary + ValuedDictZipper).
With the dictionary layer complete, three research initiatives have been identified for advanced optimization:
See: Research Initiatives for detailed plans and methodology.
These represent substantial multi-week research projects with phased approaches and early exit criteria based on empirical data. Each follows the scientific method with hypothesis formation, controlled experiments, and data-driven decisions.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |