use arcweight::prelude::*;
fn build_edit_distance_fst(
source: &str,
target: &str,
insertion_cost: f32,
deletion_cost: f32,
substitution_cost: f32,
) -> VectorFst<TropicalWeight> {
let mut fst = VectorFst::new();
let source_chars: Vec<char> = source.chars().collect();
let target_chars: Vec<char> = target.chars().collect();
let m = source_chars.len();
let n = target_chars.len();
let mut states = vec![vec![]; m + 1];
for state_row in states.iter_mut().take(m + 1) {
for _j in 0..=n {
state_row.push(fst.add_state());
}
}
fst.set_start(states[0][0]);
fst.set_final(states[m][n], TropicalWeight::one());
#[allow(clippy::needless_range_loop)] for i in 0..=m {
for j in 0..=n {
let current = states[i][j];
if i < m && j < n {
let cost = if source_chars[i] == target_chars[j] {
0.0 } else {
substitution_cost };
fst.add_arc(
current,
Arc::new(
source_chars[i] as u32,
target_chars[j] as u32,
TropicalWeight::new(cost),
states[i + 1][j + 1],
),
);
}
if i < m {
fst.add_arc(
current,
Arc::new(
source_chars[i] as u32,
0, TropicalWeight::new(deletion_cost),
states[i + 1][j],
),
);
}
if j < n {
fst.add_arc(
current,
Arc::new(
0, target_chars[j] as u32,
TropicalWeight::new(insertion_cost),
states[i][j + 1],
),
);
}
}
}
fst
}
fn compute_edit_distance(
source: &str,
target: &str,
insertion_cost: f32,
deletion_cost: f32,
substitution_cost: f32,
) -> Result<f32> {
let edit_fst = build_edit_distance_fst(
source,
target,
insertion_cost,
deletion_cost,
substitution_cost,
);
let config = ShortestPathConfig::default();
let shortest: VectorFst<TropicalWeight> = shortest_path(&edit_fst, config)?;
if let Some(start) = shortest.start() {
let mut stack = vec![(start, 0.0)];
let mut visited = std::collections::HashSet::new();
while let Some((state, cost)) = stack.pop() {
if visited.contains(&state) {
continue;
}
visited.insert(state);
if shortest.is_final(state) {
return Ok(cost);
}
for arc in shortest.arcs(state) {
stack.push((arc.nextstate, cost + arc.weight.value()));
}
}
}
Ok(f32::INFINITY)
}
fn main() -> Result<()> {
println!("Edit Distance Computation Example");
println!("=================================\n");
let test_pairs = vec![
("kitten", "sitting"),
("saturday", "sunday"),
("hello", "hallo"),
("abc", "abc"),
("abc", "def"),
("", "abc"),
("abc", ""),
];
println!("1. Standard Levenshtein Distance (all operations cost 1.0):");
println!("-----------------------------------------------------------");
for (source, target) in &test_pairs {
let distance = compute_edit_distance(source, target, 1.0, 1.0, 1.0)?;
if distance.is_finite() {
println!(" '{source}' -> '{target}': {distance}");
} else {
println!(" '{source}' -> '{target}': No transformation found");
}
}
println!("\n2. Custom Weights (insert=0.5, delete=2.0, substitute=1.0):");
println!("------------------------------------------------------------");
for (source, target) in &test_pairs {
let distance = compute_edit_distance(source, target, 0.5, 2.0, 1.0)?;
if distance.is_finite() {
println!(" '{source}' -> '{target}': {distance}");
} else {
println!(" '{source}' -> '{target}': No transformation found");
}
}
println!("\n3. Expensive Substitutions (insert=1.0, delete=1.0, substitute=3.0):");
println!("--------------------------------------------------------------------");
for (source, target) in &test_pairs {
let distance = compute_edit_distance(source, target, 1.0, 1.0, 3.0)?;
if distance.is_finite() {
println!(" '{source}' -> '{target}': {distance}");
} else {
println!(" '{source}' -> '{target}': No transformation found");
}
}
println!("\n4. Detailed Analysis: 'kitten' -> 'sitting'");
println!("--------------------------------------------");
println!("This transformation requires:");
println!("- Substitute 'k' -> 's'");
println!("- Insert 'i' after 's'");
println!("- Keep 'itten' -> 'itting' (substitute 'e' -> 'i')");
println!("- Add 'g' at the end\n");
let configs = vec![
("Uniform weights", 1.0, 1.0, 1.0),
("Cheap insertions", 0.5, 1.0, 1.0),
("Cheap deletions", 1.0, 0.5, 1.0),
("Cheap substitutions", 1.0, 1.0, 0.5),
];
for (name, ins, del, sub) in configs {
let distance = compute_edit_distance("kitten", "sitting", ins, del, sub)?;
if distance.is_finite() {
println!(" {name}: {distance} (ins={ins}, del={del}, sub={sub})");
} else {
println!(" {name}: No transformation found (ins={ins}, del={del}, sub={sub})");
}
}
println!("\n5. Step-by-step transformations:");
println!("---------------------------------");
let examples = vec![
("cat", "cut", "Substitute 'a' -> 'u'"),
("cat", "cats", "Insert 's' at end"),
("cats", "cat", "Delete 's' from end"),
("cat", "dog", "Replace all characters"),
];
for (source, target, description) in examples {
let distance = compute_edit_distance(source, target, 1.0, 1.0, 1.0)?;
println!(" '{source}' -> '{target}': {description} (distance: {distance})");
}
Ok(())
}