detcore/edit_distance/
damerau_levenshtein.rs1use core::hash::Hash;
10use std::collections::HashMap;
11
12fn index(row_length: usize, row: usize, col: usize) -> usize {
13 row_length * row + col
14}
15
16fn longest_common_prefix<T: PartialEq>(a: &[T], b: &[T]) -> usize {
17 let mut count = 0;
18
19 let mut first = a.iter();
20 let mut second = b.iter();
21
22 while first.next() == second.next() {
23 count += 1;
24 }
25
26 count
27}
28
29fn longest_common_suffix<T: PartialEq>(a: &[T], b: &[T]) -> usize {
30 let mut count = 0;
31
32 let mut first = a.iter().rev();
33 let mut second = b.iter().rev();
34
35 while first.next() == second.next() {
36 count += 1;
37 }
38
39 count
40}
41
42pub fn damerau_lev<T>(mut source: &[T], mut target: &[T]) -> usize
46where
47 T: PartialEq + Hash + Eq,
48{
49 let prefix = longest_common_prefix(source, target);
50 source = &source[prefix..];
51 target = &target[prefix..];
52
53 let suffix = longest_common_suffix(source, target);
54 source = &source[..source.len() - suffix];
55 target = &target[..target.len() - suffix];
56
57 let m = source.len();
58 let n = target.len();
59
60 if m == 0 {
61 return n;
62 }
63 if n == 0 {
64 return m;
65 }
66
67 let width = n + 2;
68 let height = m + 2;
69
70 let mut matrix: Vec<usize> = vec![0; width * height];
71
72 let inf: usize = m + n;
73
74 matrix[0] = inf;
75 for i in 0..=m {
76 matrix[index(width, i + 1, 1)] = i;
77 matrix[index(width, i + 1, 0)] = inf;
78 }
79
80 for j in 0..=n {
81 matrix[index(width, 1, j + 1)] = j;
82 matrix[index(width, 0, j + 1)] = inf;
83 }
84
85 let mut last_row = HashMap::<&T, usize>::new();
86
87 for row in 1..=m {
88 let ch_s = &source[row - 1];
89 let mut last_match_col = 0;
90
91 for col in 1..=n {
92 let ch_t = &target[col - 1];
93 let last_match_row = *last_row.get(&ch_t).unwrap_or(&0);
94
95 #[allow(clippy::bool_to_int_with_if)]
96 let cost = if ch_s == ch_t { 0 } else { 1 };
97
98 let dist_add = matrix[index(width, row, col + 1)] + 1;
99 let dist_del = matrix[index(width, row + 1, col)] + 1;
100 let dist_sub = matrix[index(width, row, col)] + cost;
101
102 let dist_trans = matrix[index(width, last_match_row, last_match_col)]
103 + (row - last_match_row - 1)
104 + 1
105 + (col - last_match_col - 1);
106
107 let mut min = dist_add;
109 if dist_del < min {
110 min = dist_del;
111 }
112 if dist_sub < min {
113 min = dist_sub;
114 }
115 if dist_trans < min {
116 min = dist_trans;
117 }
118
119 matrix[index(width, row + 1, col + 1)] = min;
121
122 if cost == 0 {
124 last_match_col = col;
125 }
126 }
127
128 last_row.insert(ch_s, row);
130 }
131
132 matrix[index(width, m + 1, n + 1)]
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[allow(unused)]
141 fn print_matrix(matrix: &[usize], cols: usize) {
142 let rows = matrix.len() / cols;
143 for i in 0..rows {
144 for j in 0..cols {
145 print!("{:#3}, ", matrix[index(cols, i, j)]);
146 }
147 println!();
148 }
149 println!();
150 }
151
152 #[test]
158 fn longest_common_prefix_test() {
159 let source = vec!['A'; 10_000];
160 let mut target = vec!['A'; 10_000];
161 *target.last_mut().unwrap() = 'B';
162 assert_eq!(longest_common_prefix(&source, &target), 9_999);
163 }
164
165 #[test]
166 fn longest_common_suffix_test() {
167 let source = vec!['A'; 10_000];
168 let mut target = vec!['A'; 10_000];
169 *target.last_mut().unwrap() = 'B';
170 assert_eq!(longest_common_suffix(&source, &target), 0);
171
172 let source = vec!['A'; 10_000];
173 let mut target = vec!['A'; 10_000];
174 *target.first_mut().unwrap() = 'B';
175 assert_eq!(longest_common_suffix(&source, &target), 9_999);
176 }
177
178 #[test]
179 fn smoke_test() {
180 assert_eq!(
181 damerau_lev(&['a', ' ', 'a', 'b', 'c', 't'], &['a', ' ', 'c', 'a', 't'],),
182 2
183 );
184 }
185
186 #[test]
187 fn long_common_prefix_same_len() {
188 let source = vec!['A'; 10_000];
189 let mut target = vec!['A'; 10_000];
190 *target.last_mut().unwrap() = 'B';
191 assert_eq!(damerau_lev(&source, &target), 1);
192 }
193
194 #[test]
195 fn long_common_suffix_same_len() {
196 let source = vec!['A'; 10_000];
197 let mut target = vec!['A'; 10_000];
198 *target.first_mut().unwrap() = 'B';
199 assert_eq!(damerau_lev(&source, &target), 1);
200 }
201
202 #[test]
203 fn long_common_suffix_diff_len() {
204 let source = vec!['A'; 10_000];
205
206 let mut target = vec!['B', 'C'];
207 target.append(&mut vec!['A'; 10_000]);
208
209 assert_eq!(damerau_lev(&source, &target), 2);
210 }
211
212 #[test]
213 fn same_length_shuffled() {
214 use rand::seq::SliceRandom;
215
216 const COUNT: usize = 1_000;
217
218 let mut rng = rand::rng();
219 let mut source: Vec<u8> = Vec::with_capacity(COUNT);
220
221 let mut x = 0;
222 for _ in 0..COUNT {
223 source.push(x);
224 x = x.wrapping_add(1)
225 }
226
227 let target = source.clone();
228
229 source.shuffle(&mut rng);
230
231 assert!(damerau_lev(&source, &target) > 500);
232 }
233}