Current Status: Infrastructure complete, ready for successor generation refactoring
Estimated Time: 2-3 hours of focused work
OperationSet Infrastructure ✅
operations field added to GeneralizedAutomatonParameter Threading ✅
GeneralizedState::transition() accepts &OperationSet parameterThe _operations parameter is threaded through but not yet used. The code still uses hardcoded standard operations in:
successors_standard() (state.rs:184-197)successors_i_type_standard() (state.rs:216-301)successors_m_type_standard() (state.rs:303-356)Replace the hardcoded standard operation logic with a generic implementation that iterates over operations.operations().
Change successors_standard() to accept operations parameter:
// BEFORE
fn successors_standard(
&self,
pos: &GeneralizedPosition,
bit_vector: &CharacteristicVector,
) -> Vec<GeneralizedPosition>
// AFTER
fn successors(
&self,
pos: &GeneralizedPosition,
operations: &crate::transducer::OperationSet,
bit_vector: &CharacteristicVector,
) -> Vec<GeneralizedPosition>
Update call site in transition():
// Line 164: Change from
let successors = self.successors_standard(pos, bit_vector);
// To
let successors = self.successors(pos, operations, bit_vector);
Test: Run tests to ensure nothing broke.
The successors_i_type_standard() method is the most complex. Refactor it incrementally:
Phase A: Extract Character Matching Logic
Create helper method:
/// Check if character at bit_vector[index] matches
fn has_match_at(&self, bit_vector: &CharacteristicVector, index: usize) -> bool {
index < bit_vector.len() && bit_vector.is_match(index)
}
Phase B: Iterate Over Operations
Replace hardcoded operation logic with iteration:
fn successors_i_type(
&self,
offset: i32,
errors: u8,
operations: &crate::transducer::OperationSet,
bit_vector: &CharacteristicVector,
) -> Vec<GeneralizedPosition> {
let mut successors = Vec::new();
let n = self.max_distance as i32;
let match_index = (offset + n) as usize;
// Position within visible window
if match_index < bit_vector.len() {
let has_match = bit_vector.is_match(match_index);
// Iterate over all operations
for op in operations.operations() {
// Only handle single-char operations for now
if op.consume_x() > 1 || op.consume_y() > 1 {
continue; // Skip multi-char for Phase 2c
}
// Classify operation type
if op.is_match() {
// Match operation: ⟨1, 1, 0.0⟩
if has_match && errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_i(offset, errors, self.max_distance) {
successors.push(succ);
// Early return: match takes precedence
return successors;
}
}
} else if op.is_deletion() {
// Delete operation: ⟨1, 0, w⟩
if !has_match && errors < self.max_distance {
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_i(offset - 1, new_errors, self.max_distance) {
successors.push(succ);
}
}
}
} else if op.is_insertion() || op.is_substitution() {
// Insert ⟨0, 1, w⟩ or Substitute ⟨1, 1, w⟩
if !has_match && errors < self.max_distance {
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_i(offset, new_errors, self.max_distance) {
successors.push(succ);
}
}
}
}
}
// SKIP-TO-MATCH optimization (Phase 2c: generalize for multi-char)
if !has_match && errors < self.max_distance {
for idx in (match_index + 1)..bit_vector.len() {
if bit_vector.is_match(idx) {
let skip_distance = (idx - match_index) as i32;
let new_errors = errors + skip_distance as u8;
if new_errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_i(offset + skip_distance, new_errors, self.max_distance) {
successors.push(succ);
}
}
break;
}
}
}
return successors;
}
// Out-of-window logic (keep as-is for now)
if errors >= self.max_distance {
return successors;
}
// ... rest of out-of-window logic ...
successors
}
Test After Each Change:
cargo test --lib generalized
Similar approach for successors_m_type_standard():
fn successors_m_type(
&self,
offset: i32,
errors: u8,
operations: &crate::transducer::OperationSet,
bit_vector: &CharacteristicVector,
) -> Vec<GeneralizedPosition> {
let mut successors = Vec::new();
// Compute match index for M-type
let bit_index = offset + bit_vector.len() as i32;
let has_match = bit_index >= 0
&& (bit_index as usize) < bit_vector.len()
&& bit_vector.is_match(bit_index as usize);
// Iterate over operations
for op in operations.operations() {
// Skip multi-char for now
if op.consume_x() > 1 || op.consume_y() > 1 {
continue;
}
if op.is_match() && has_match {
if let Ok(succ) = GeneralizedPosition::new_m(offset + 1, errors, self.max_distance) {
successors.push(succ);
}
} else if op.is_deletion() && errors < self.max_distance {
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_m(offset, new_errors, self.max_distance) {
successors.push(succ);
}
}
} else if (op.is_insertion() || op.is_substitution()) && errors < self.max_distance {
let new_errors = errors + op.weight() as u8;
if new_errors <= self.max_distance {
if let Ok(succ) = GeneralizedPosition::new_m(offset + 1, new_errors, self.max_distance) {
successors.push(succ);
}
}
}
}
successors
}
Once operations is actually used:
// state.rs:151 - Remove underscore prefix
pub fn transition(
&self,
operations: &crate::transducer::OperationSet, // Was: _operations
bit_vector: &CharacteristicVector,
_input_length: usize,
) -> Option<Self>
# Run all generalized tests
cargo test --lib generalized
# Should see: 57 passed; 0 failed
Verify that using OperationSet::standard() produces identical results to Phase 1:
#[test]
fn test_operation_set_standard_equivalence() {
let automaton_phase2 = GeneralizedAutomaton::new(2);
let test_cases = vec![
("test", "test"),
("test", "text"),
("test", "tests"),
("tests", "test"),
("test", "tst"),
];
for (word, input) in test_cases {
let result = automaton_phase2.accepts(word, input);
// Should match Phase 1 behavior
assert_eq!(result, expected_for_distance_2(word, input));
}
}
Match Operation Precedence
SKIP-TO-MATCH Optimization
Out-of-Window Position Handling
input.len() > word.len()Error Budget Tracking
op.weight() as u8 to accumulate errorsnew_errors <= self.max_distance before adding successorI^ε Conversion
offset - 1offsetAfter each incremental change:
cargo test --lib generalized
If tests fail:
test_debug_* tests)Cross-validation script: Use /tmp/test_cross_validation.rs pattern
_operations parameter is now operations (no underscore)operations.operations()successors_standard() → successors()successors_i_type_standard() → successors_i_type()successors_m_type_standard() → successors_m_type()operations parameter to all threeTotal: 2-3 hours
Next phases (Phase 2c-e):
can_apply() checks)src/transducer/generalized/state.rs:184-356src/transducer/operation_type.rs:285-307src/transducer/operation_set.rs:144-146docs/generalized/phase2_implementation_plan.mddocs/generalized/phase2_progress.mdGood luck! The infrastructure is solid, this is just methodical refactoring work.
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 |