1use std::cmp::min;
2use std::error::Error;
3use std::fmt;
4
5use crate::util::DistanceMatrix;
6
7#[derive(Debug)]
10pub enum LevenshteinError {
11 InvalidDistanceMatrixError,
13}
14
15impl fmt::Display for LevenshteinError {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 let error = match self {
18 LevenshteinError::InvalidDistanceMatrixError => "Invalid matrix error",
19 };
20
21 write!(f, "{}", error)
22 }
23}
24
25impl Error for LevenshteinError {}
26
27#[derive(Clone, PartialEq)]
29pub enum Edit<T: PartialEq> {
30 Delete(usize), Insert(usize, T), Substitute(usize, T), }
34
35pub fn apply_edits<T: Clone + PartialEq>(source: &[T], edits: &[Edit<T>]) -> Vec<T> {
65 let mut target_constructor: Vec<Option<T>> =
67 source.iter().map(|item| Some(item.clone())).collect();
68
69 let mut inserts = Vec::<Edit<T>>::with_capacity(source.len());
70
71 for edit in edits.iter().rev() {
75 match edit {
76 Edit::Substitute(idx, val) => target_constructor[idx - 1] = Some(val.clone()),
77 Edit::Delete(idx) => target_constructor[idx - 1] = None,
78 Edit::Insert(idx, val) => inserts.push(Edit::Insert(*idx, val.clone())),
79 }
80 }
81
82 for i in &inserts {
83 if let Edit::Insert(idx, val) = i {
84 target_constructor.insert(*idx, Some(val.clone()));
85 }
86 }
87
88 let mut target = Vec::<T>::new();
89 for i in &target_constructor {
90 match i {
91 Some(val) => target.push(val.clone()),
92 None => (),
93 }
94 }
95
96 target
97}
98
99pub fn generate_edits<T: Clone + PartialEq>(
122 source: &[T],
123 target: &[T],
124 distances: &DistanceMatrix,
125) -> Result<Vec<Edit<T>>, LevenshteinError> {
126 let mut source_idx = source.len();
127 let mut target_idx = target.len();
128
129 if source_idx + 1 != distances.len() || target_idx + 1 != distances[0].len() {
130 return Err(LevenshteinError::InvalidDistanceMatrixError);
131 }
132
133 let mut edits = Vec::<Edit<T>>::new();
134
135 while source_idx != 0 || target_idx != 0 {
138 let current_item = distances[source_idx][target_idx];
139
140 let substitute = if source_idx > 0 && target_idx > 0 {
142 distances[source_idx - 1][target_idx - 1]
143 } else {
144 usize::MAX
145 };
146
147 let delete = if source_idx > 0 {
148 distances[source_idx - 1][target_idx]
149 } else {
150 usize::MAX
151 };
152
153 let insert = if target_idx > 0 {
154 distances[source_idx][target_idx - 1]
155 } else {
156 usize::MAX
157 };
158
159 let min = min(min(insert, delete), substitute);
160
161 if min == current_item {
162 source_idx = source_idx - 1;
163 target_idx = target_idx - 1;
164 } else if min == current_item - 1 {
165 if min == insert {
166 edits.push(Edit::Insert(source_idx, target[target_idx - 1].clone()));
169 target_idx = target_idx - 1;
170 } else if min == delete {
171 edits.push(Edit::Delete(source_idx));
172 source_idx = source_idx - 1;
173 } else if min == substitute {
174 edits.push(Edit::Substitute(source_idx, target[target_idx - 1].clone()));
175 source_idx = source_idx - 1;
176 target_idx = target_idx - 1;
177 } else {
178 return Err(LevenshteinError::InvalidDistanceMatrixError);
179 };
180 } else {
181 return Err(LevenshteinError::InvalidDistanceMatrixError);
182 };
183 }
184
185 Ok(edits)
186}
187
188#[cfg(test)]
189mod tests {
190 use crate::edit::*;
191
192 fn do_vecs_match<T: PartialEq>(a: &Vec<T>, b: &Vec<T>) -> bool {
195 let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count();
196 matching == a.len() && matching == b.len()
197 }
198
199 #[test]
200 fn edit_list_is_correct() {
201 let s1 = "SATURDAY";
202 let s2 = "SUNDAY";
203
204 let distances = vec![
207 vec![0, 1, 2, 3, 4, 5, 6],
208 vec![1, 0, 1, 2, 3, 4, 5],
209 vec![2, 1, 1, 2, 3, 3, 4],
210 vec![3, 2, 2, 2, 3, 4, 4],
211 vec![4, 3, 2, 3, 3, 4, 5],
212 vec![5, 4, 3, 3, 4, 4, 5],
213 vec![6, 5, 4, 4, 3, 4, 5],
214 vec![7, 6, 5, 5, 4, 3, 4],
215 vec![8, 7, 6, 6, 5, 4, 3],
216 ];
217
218 let expected_edits = vec![
219 Edit::<u8>::Substitute(5, 78),
220 Edit::<u8>::Delete(3),
221 Edit::<u8>::Delete(2),
222 ];
223
224 let edits = generate_edits(s1.as_bytes(), s2.as_bytes(), &distances).unwrap();
225
226 assert_eq!(do_vecs_match(&edits, &expected_edits), true);
227 }
228
229 #[test]
230 fn edits_are_applied_correctly() {
231 let s1 = "SATURDAY";
232 let expected_s2 = "SUNDAY";
233
234 let mut edits = vec![
236 Edit::<u8>::Substitute(5, 78),
237 Edit::<u8>::Delete(3),
238 Edit::<u8>::Delete(2),
239 ];
240
241 let s2_bytes_vec = apply_edits(s1.as_bytes(), &mut edits);
242
243 let s2 = match std::str::from_utf8(&s2_bytes_vec) {
244 Ok(v) => v,
245 Err(_) => panic!("Not a valid UTF-8 sequence!"),
246 };
247
248 assert_eq!(s2, expected_s2);
249 }
250}