Liking cljdoc? Tell your friends :D

CompressedSuffixAutomaton Status

Status: ARCHIVED EXPERIMENTAL / INCOMPLETE

The CompressedSuffixAutomaton is a proof-of-concept implementation demonstrating that compression techniques can be applied to suffix automata. However, it has known defects and is not ready for production use.

What Works ✅

  • [x] Single-text suffix automaton construction
  • [x] Substring matching for single text
  • [x] Dictionary trait implementation
  • [x] All unit tests passing (5/5)
  • [x] Memory compression (~26% savings vs original)
  • [x] Integrated into prelude and module structure

Known Issues ❌

1. Generalized Suffix Automaton Broken

Problem: Building suffix automaton from multiple texts doesn't work correctly.

Symptoms:

  • Substrings from second and subsequent texts may not be found
  • Example: from_texts(vec!["text1", "text2"]) may not find substrings from "text2"

Root Cause: The online construction algorithm for generalized suffix automaton is not correctly implemented. The issue is likely in how we handle state cloning and suffix link updates when transitioning between texts.

What Needs Fixing:

  • Study the original SuffixAutomaton implementation more carefully
  • Understand how it handles the transition between texts
  • Possibly need to add special "end-of-text" markers or state transitions
  • Debug the extend() method's handling of state cloning

Test Case to Fix:

let sa = CompressedSuffixAutomaton::from_texts(vec![
    "Pack my box with five dozen liquor jugs",
    "The quick brown fox jumps",
]);
assert!(sa.contains("box"));  // Currently fails!
assert!(sa.contains("quick")); // Currently fails!

2. Fuzzy Matching Untested

Problem: Integration with Levenshtein automaton via Transducer is untested and may not work.

Symptoms:

  • Fuzzy substring queries return no results even when exact matches exist

What Needs Testing:

let sa = CompressedSuffixAutomaton::from_text("testing");
let transducer = Transducer::new(sa, Algorithm::Standard);

// Should find "testing" with distance 1
let results: Vec<_> = transducer.query("testng", 1).collect();
assert!(!results.is_empty()); // Currently fails?

What Needs Fixing:

  • Test with single-text suffix automaton first
  • Verify DictionaryNode trait implementation works correctly with Transducer
  • Check that edge iteration returns edges in the correct format
  • Debug any issues with state transitions during Levenshtein traversal

3. Memory Savings Less Than Expected

Problem: Only achieving ~26% memory savings instead of theoretical ~50%.

Current: ~52 bytes/state Original: ~48 bytes/state (with HashMap) Expected: ~20-30 bytes/state

Root Cause: Vec overhead is 24 bytes per Vec, which dominates the savings from smaller integer types.

Possible Solutions:

  1. Use SmallVec for edges (inline small edge sets)
  2. Use fixed-size arrays for common edge counts (1-3 edges)
  3. Implement true DAT BASE/CHECK arrays (complex, requires handling relocation)
  4. Pack edges into a single contiguous array with state-to-range mapping

Trade-offs:

  • More complexity vs. better compression
  • Construction time vs. memory savings
  • Runtime performance vs. memory efficiency

Future Enhancements 🚀

1. Implement True DAT Compression

Replace Vec<(u8, u32)> edges with BASE/CHECK arrays:

struct TrueCompressedSuffixAutomaton {
    base: Vec<i32>,      // BASE[state] + byte = next_state
    check: Vec<i32>,     // CHECK[next_state] == state (validation)
    suffix_link: Vec<u32>,
    max_length: Vec<u16>,
    flags: Vec<u8>,
}

Benefits:

  • O(1) edge lookup (vs current O(log k))
  • ~12-16 bytes/state (vs current ~52)
  • Better cache locality

Challenges:

  • BASE relocation during online construction is complex
  • Need to handle conflicts when adding edges
  • Requires implementing subtree relocation algorithm

2. Add Serialization Support

Implement DictionaryFromTerms trait:

#[cfg(feature = "serialization")]
impl crate::serialization::DictionaryFromTerms for CompressedSuffixAutomaton {
    fn from_terms<I: IntoIterator<Item = String>>(terms: I) -> Self {
        CompressedSuffixAutomaton::from_texts(terms)
    }
}

Blocked By: Need to fix generalized suffix automaton first.

3. Benchmark vs Original

Once generalized SA is fixed, benchmark:

// benches/suffix_automaton_comparison.rs
fn bench_compressed_vs_original(c: &mut Criterion) {
    let texts = load_large_corpus(); // 100+ texts

    // Construction time
    // Memory usage
    // Query performance (exact and fuzzy)
}

4. Position Tracking

Add position metadata like original SuffixAutomaton:

struct CompressedSuffixAutomatonInner {
    // ... existing fields
    positions: HashMap<usize, Vec<(usize, usize)>>, // (text_id, position)
}

Use Case: Finding where in the original texts a substring occurs.

Priority

  1. HIGH: Fix generalized suffix automaton
  2. MEDIUM: Test and fix fuzzy matching
  3. LOW: Improve memory compression
  4. LOW: Add benchmarks and position tracking

References

  • Original implementation: src/dictionary/suffix_automaton.rs
  • Suffix automaton algorithm: Blumer et al. (1985)
  • Design doc: docs/SUFFIX_AUTOMATON_DESIGN.md

Decision: Keep or Remove?

Arguments for Keeping

  1. Demonstrates feasibility - Proves compression can be applied to suffix automata
  2. Learning resource - Shows how to implement compressed string structures
  3. Future potential - Could be completed and become useful
  4. Already integrated - Code is written, tested, documented

Arguments for Removing

  1. Not production-ready - Has known defects
  2. Minimal savings - Only 26% compression isn't compelling
  3. Maintenance burden - Another data structure to maintain
  4. User confusion - Might use experimental code accidentally

Recommendation

KEEP but clearly marked as experimental:

  • ✅ Already has warnings in documentation
  • ✅ Marked in README as experimental
  • ✅ All unit tests pass
  • ✅ Could be completed in future work

Completion Estimate

  • Fix generalized SA: 4-8 hours
  • Test fuzzy matching: 2-4 hours
  • Add benchmarks: 2-3 hours
  • Improve compression: 8-16 hours (if doing true DAT)

Total: 16-31 hours to make production-ready


Last Updated: 2025-01-XX Status: Experimental / Incomplete Recommendation: Use SuffixAutomaton for production

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close