1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Levenshtein distance algorithm variants.
use crate::transducer::OperationSet;
/// Levenshtein distance algorithm type.
///
/// Different algorithms support different edit operations and are
/// suited for different use cases.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
#[derive(Default)]
pub enum Algorithm {
/// Standard Levenshtein distance.
///
/// Supports three edit operations:
/// - Insert: add a character
/// - Delete: remove a character
/// - Substitute: replace one character with another
///
/// This is the classic edit distance metric.
#[default]
Standard,
/// Levenshtein distance with transposition.
///
/// Extends Standard with:
/// - Transpose: swap two adjacent characters
///
/// Useful for catching common typos where adjacent letters are swapped.
Transposition,
/// Levenshtein distance with merge and split operations.
///
/// Extends Standard with:
/// - Merge: combine two characters into one
/// - Split: expand one character into two
///
/// Useful for OCR errors and other character-level transformations.
MergeAndSplit,
}
impl Algorithm {
/// Get a human-readable name for this algorithm
pub fn name(&self) -> &'static str {
match self {
Algorithm::Standard => "standard",
Algorithm::Transposition => "transposition",
Algorithm::MergeAndSplit => "merge-and-split",
}
}
/// Check if this algorithm supports transposition operations
pub fn supports_transposition(&self) -> bool {
matches!(self, Algorithm::Transposition | Algorithm::MergeAndSplit)
}
/// Check if this algorithm supports merge/split operations
pub fn supports_merge_split(&self) -> bool {
matches!(self, Algorithm::MergeAndSplit)
}
/// Convert this algorithm to an OperationSet
///
/// Maps the enum variant to the corresponding operation set configuration.
/// This enables backward compatibility with the generalized operations framework.
///
/// # Returns
///
/// An `OperationSet` containing the operations for this algorithm.
///
/// # Examples
///
/// ```rust
/// # use liblevenshtein::transducer::Algorithm;
/// let alg = Algorithm::Standard;
/// let ops = alg.to_operation_set();
/// assert_eq!(ops.len(), 4); // Match, Substitute, Insert, Delete
///
/// let alg = Algorithm::Transposition;
/// let ops = alg.to_operation_set();
/// assert_eq!(ops.len(), 5); // Standard + Transposition
/// ```
pub fn to_operation_set(&self) -> OperationSet {
match self {
Algorithm::Standard => OperationSet::standard(),
Algorithm::Transposition => OperationSet::with_transposition(),
Algorithm::MergeAndSplit => OperationSet::with_merge_split(),
}
}
}
impl std::fmt::Display for Algorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
impl std::str::FromStr for Algorithm {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"standard" => Ok(Algorithm::Standard),
"transposition" | "trans" => Ok(Algorithm::Transposition),
"merge-and-split" | "mergesplit" | "merge" => Ok(Algorithm::MergeAndSplit),
_ => Err(format!(
"Unknown algorithm: {}. Valid options: standard, transposition, merge-and-split",
s
)),
}
}
}
impl From<Algorithm> for OperationSet {
/// Convert an Algorithm to an OperationSet
///
/// Enables seamless conversion from the legacy enum-based API to the
/// generalized operations framework. This is the preferred conversion path.
///
/// # Examples
///
/// ```rust
/// # use liblevenshtein::transducer::{Algorithm, OperationSet};
/// let ops: OperationSet = Algorithm::Standard.into();
/// assert_eq!(ops.len(), 4);
/// ```
fn from(algorithm: Algorithm) -> Self {
algorithm.to_operation_set()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_operation_set_standard() {
let ops = Algorithm::Standard.to_operation_set();
assert_eq!(ops.len(), 4); // Match, Substitute, Insert, Delete
}
#[test]
fn test_to_operation_set_transposition() {
let ops = Algorithm::Transposition.to_operation_set();
assert_eq!(ops.len(), 5); // Standard + Transposition
}
#[test]
fn test_to_operation_set_merge_split() {
let ops = Algorithm::MergeAndSplit.to_operation_set();
assert_eq!(ops.len(), 6); // Standard + Merge + Split
}
#[test]
fn test_from_algorithm_to_operation_set() {
let ops: OperationSet = Algorithm::Standard.into();
assert_eq!(ops.len(), 4);
let ops: OperationSet = Algorithm::Transposition.into();
assert_eq!(ops.len(), 5);
let ops: OperationSet = Algorithm::MergeAndSplit.into();
assert_eq!(ops.len(), 6);
}
}