Philosophy: Construct entire DAWG upfront, use immutably
// Phase 1: Collection (DAWG not usable yet)
List<String> terms = new ArrayList<>();
terms.add("apple");
terms.add("banana");
terms.add("cherry");
// Phase 2: Construction (requires ALL terms, sorted)
Collections.sort(terms);
DAWG dawg = new DAWGBuilder().build(terms);
// Phase 3: Usage (NOW it works, but immutable)
List<String> results = dawg.query("aple", 2);
// ❌ Cannot modify after construction
// dawg.insert("apricot"); // Not supported!
Key Requirements:
DawgDictionary)Philosophy: Same as Java - immutable perfection
// Build once from complete term list
let dict = DawgDictionary::from_iter(vec![
"apple", "banana", "cherry"
]);
// Usable immediately, perfectly minimal
let transducer = Transducer::new(dict, Algorithm::Standard);
let results: Vec<_> = transducer.query("aple", 2).collect();
// ❌ Cannot modify (immutable)
Advantages over Java:
DynamicDawg)Philosophy: Immediate usability + optional optimization
// Phase 1: Start empty - ALREADY USABLE!
let dawg = DynamicDawg::new();
let transducer = Transducer::new(dawg.clone(), Algorithm::Standard);
// Phase 2: Incremental construction - STILL USABLE!
dawg.insert("apple");
let r1: Vec<_> = transducer.query("aple", 2).collect(); // Works!
dawg.insert("banana");
let r2: Vec<_> = transducer.query("banan", 1).collect(); // Works!
// Phase 3: Modification - ALWAYS USABLE!
dawg.remove("apple");
dawg.insert("apricot");
let r3: Vec<_> = transducer.query("apri", 2).collect(); // Works!
// Phase 4: Optimization (optional, but recommended)
dawg.compact(); // NOW it's as minimal as Java's!
Key Features:
compact() achieves Java-level minimalitycompact() Achieves Static DAWG QualityThe compact() method recreates what Java's static builder does:
pub fn compact(&self) -> usize {
// 1. Extract all current terms
let terms = extract_all_terms();
// 2. Sort them (CRITICAL for minimality!)
terms.sort();
// 3. Rebuild from scratch with sorted input
rebuild_from_sorted(terms);
}
This is identical to Java's approach:
public static DAWG build(List<String> terms) {
Collections.sort(terms); // Step 2
return buildFromSorted(terms); // Step 3
}
DAWG minimization relies on incremental suffix sharing during construction:
Terms (sorted): ["band", "banana", "bandana"]
Construction:
1. Insert "band"
b-a-n-d[*]
2. Insert "banana" - shares "ban" prefix
b-a-n-d[*]
\
a-n-a[*]
3. Insert "bandana" - shares "ban" + recognizes "ana" suffix
b-a-n-d[*]
\-a-n-a[*] (shared suffix!)
Terms (unsorted): ["banana", "band", "bandana"]
1. Insert "banana"
b-a-n-a-n-a[*]
2. Insert "band"
b-a-n-a-n-a[*]
\-d[*]
3. Insert "bandana"
b-a-n-a-n-a[*]
\-d[*]
\-a-n-a[*] (duplicates "ana"!)
Result: Unsorted insertion creates duplicate suffixes = larger structure!
| Aspect | Static DAWG | DynamicDawg (no compact) | DynamicDawg (after compact) |
|---|---|---|---|
| Construction | O(n log n) sort + O(n) build | O(mn) incremental | O(n log n) + O(n) |
| Minimality | Perfect | Near-minimal | Perfect |
| Space | Minimal | 1.0x - 1.5x minimal | Minimal |
| Modifications | ❌ Rebuild required | ✅ O(m) per op | ✅ O(m) per op |
| Usability | After complete build | Immediate | Immediate |
DawgDictionary) When:✅ Fixed dictionary
✅ Maximum space efficiency required
✅ Predictable lifecycle
Example: Mobile app with built-in dictionary
DynamicDawg) When:✅ Changing dictionary
✅ Immediate usability needed
✅ Batch updates
Example: IDE with custom dictionary per project
If you currently use static DAWG but need modifications:
// Before: Static (rebuild on change)
fn update_dictionary(old_terms: Vec<String>, new_term: String) -> DawgDictionary {
let mut all_terms = old_terms;
all_terms.push(new_term);
DawgDictionary::from_iter(all_terms) // Full rebuild!
}
// After: Dynamic (incremental update)
fn update_dictionary(dawg: &DynamicDawg, new_term: String) {
dawg.insert(&new_term); // Just add it!
// Compact periodically, not every time
}
needs_compaction()// Start with static base dictionary
let base_terms = load_standard_dictionary();
let dawg = DynamicDawg::from_iter(base_terms);
// Add user customizations dynamically
dawg.insert("userterm1");
dawg.insert("userterm2");
// Compact before saving
dawg.compact();
save_dictionary(&dawg);
From Schulz & Mihov (2002):
"For a sorted sequence of words w₁, w₂, ..., wₙ, the minimal DAWG can be constructed incrementally in O(n) time by:
- Identifying common prefixes with previous word
- Minimizing the suffix not shared
- Adding the unique suffix"
The key insight: Lexicographic order ensures maximum prefix reuse.
Our compact() method achieves this by:
This is mathematically equivalent to building from scratch with sorted input.
Both implementations have their place:
compact(): Bridges the gap - dynamic flexibility with static efficiencyThe choice depends on your use case, not on implementation quality!
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 |