rapidfuzz/details/
distance.rs

1use crate::details::common::norm_sim_to_norm_dist;
2use crate::HashableChar;
3
4pub trait MetricUsize2 {
5    fn maximum(&self, len1: usize, len2: usize) -> usize;
6
7    fn _distance<Iter1, Iter2>(
8        &self,
9        s1: Iter1,
10        len1: usize,
11        s2: Iter2,
12        len2: usize,
13        score_cutoff: Option<usize>,
14        score_hint: Option<usize>,
15    ) -> Option<usize>
16    where
17        Iter1: DoubleEndedIterator + Clone,
18        Iter2: DoubleEndedIterator + Clone,
19        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
20        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
21    {
22        let maximum = self.maximum(len1, len2);
23
24        let cutoff_similarity = score_cutoff.map(|x| if maximum >= x { maximum - x } else { 0 });
25        let hint_similarity = score_hint.map(|x| if maximum >= x { maximum - x } else { 0 });
26
27        let sim = self._similarity(s1, len1, s2, len2, cutoff_similarity, hint_similarity)?;
28        let dist = maximum - sim;
29
30        if let Some(cutoff) = score_cutoff {
31            if dist > cutoff {
32                return None;
33            }
34        }
35        Some(dist)
36    }
37
38    fn _similarity<Iter1, Iter2>(
39        &self,
40        s1: Iter1,
41        len1: usize,
42        s2: Iter2,
43        len2: usize,
44        score_cutoff: Option<usize>,
45        mut score_hint: Option<usize>,
46    ) -> Option<usize>
47    where
48        Iter1: DoubleEndedIterator + Clone,
49        Iter2: DoubleEndedIterator + Clone,
50        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
51        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
52    {
53        let maximum = self.maximum(len1, len2);
54        if let Some(cutoff) = score_cutoff {
55            if maximum < cutoff {
56                return None;
57            }
58
59            if let Some(hint) = score_hint {
60                score_hint = Some(hint.min(cutoff));
61            }
62        }
63
64        let cutoff_distance = score_cutoff.map(|x| maximum - x);
65        let hint_distance = score_hint.map(|x| maximum - x);
66        let dist = self._distance(s1, len1, s2, len2, cutoff_distance, hint_distance)?;
67        let sim = maximum - dist;
68        if let Some(cutoff) = score_cutoff {
69            if sim < cutoff {
70                return None;
71            }
72        }
73        Some(sim)
74    }
75
76    fn _normalized_distance<Iter1, Iter2>(
77        &self,
78        s1: Iter1,
79        len1: usize,
80        s2: Iter2,
81        len2: usize,
82        mut score_cutoff: Option<f64>,
83        score_hint: Option<f64>,
84    ) -> Option<f64>
85    where
86        Iter1: DoubleEndedIterator + Clone,
87        Iter2: DoubleEndedIterator + Clone,
88        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
89        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
90    {
91        let maximum = self.maximum(len1, len2);
92
93        let cutoff_distance;
94        if let Some(mut cutoff) = score_cutoff {
95            cutoff = cutoff.clamp(0.0, 1.0);
96            score_cutoff = Some(cutoff);
97            cutoff_distance = Some((maximum as f64 * cutoff).ceil() as usize);
98        } else {
99            cutoff_distance = None;
100        }
101
102        let hint_distance;
103        if let Some(mut cutoff) = score_hint {
104            cutoff = cutoff.clamp(0.0, 1.0);
105            hint_distance = Some((maximum as f64 * cutoff).ceil() as usize);
106        } else {
107            hint_distance = None;
108        }
109
110        let dist = self._distance(s1, len1, s2, len2, cutoff_distance, hint_distance)?;
111        let norm_dist = if maximum == 0 {
112            0.0
113        } else {
114            dist as f64 / maximum as f64
115        };
116        if let Some(cutoff) = score_cutoff {
117            if norm_dist > cutoff {
118                return None;
119            }
120        }
121        Some(norm_dist)
122    }
123
124    fn _normalized_similarity<Iter1, Iter2>(
125        &self,
126        s1: Iter1,
127        len1: usize,
128        s2: Iter2,
129        len2: usize,
130        score_cutoff: Option<f64>,
131        score_hint: Option<f64>,
132    ) -> Option<f64>
133    where
134        Iter1: DoubleEndedIterator + Clone,
135        Iter2: DoubleEndedIterator + Clone,
136        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
137        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
138    {
139        let cutoff_score = score_cutoff.map(norm_sim_to_norm_dist);
140        let hint_score = score_hint.map(norm_sim_to_norm_dist);
141
142        let norm_dist = self._normalized_distance(s1, len1, s2, len2, cutoff_score, hint_score)?;
143        let norm_sim = 1.0 - norm_dist;
144
145        if let Some(cutoff) = score_cutoff {
146            if norm_sim < cutoff {
147                return None;
148            }
149        }
150        Some(norm_sim)
151    }
152}
153
154pub trait MetricUsize {
155    fn maximum(&self, len1: usize, len2: usize) -> usize;
156
157    fn _distance<Iter1, Iter2>(
158        &self,
159        s1: Iter1,
160        len1: usize,
161        s2: Iter2,
162        len2: usize,
163        score_cutoff: Option<usize>,
164        score_hint: Option<usize>,
165    ) -> usize
166    where
167        Iter1: DoubleEndedIterator + Clone,
168        Iter2: DoubleEndedIterator + Clone,
169        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
170        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
171    {
172        let maximum = self.maximum(len1, len2);
173
174        let cutoff_similarity = score_cutoff.map(|x| if maximum >= x { maximum - x } else { 0 });
175        let hint_similarity = score_hint.map(|x| if maximum >= x { maximum - x } else { 0 });
176
177        let sim = self._similarity(s1, len1, s2, len2, cutoff_similarity, hint_similarity);
178        maximum - sim
179    }
180
181    fn _similarity<Iter1, Iter2>(
182        &self,
183        s1: Iter1,
184        len1: usize,
185        s2: Iter2,
186        len2: usize,
187        score_cutoff: Option<usize>,
188        mut score_hint: Option<usize>,
189    ) -> usize
190    where
191        Iter1: DoubleEndedIterator + Clone,
192        Iter2: DoubleEndedIterator + Clone,
193        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
194        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
195    {
196        let maximum = self.maximum(len1, len2);
197        if let Some(cutoff) = score_cutoff {
198            if cutoff > maximum {
199                return maximum;
200            }
201
202            if let Some(hint) = score_hint {
203                score_hint = Some(hint.min(cutoff));
204            }
205        }
206
207        let cutoff_distance = score_cutoff.map(|x| maximum - x);
208        let hint_distance = score_hint.map(|x| maximum - x);
209        let dist = self._distance(s1, len1, s2, len2, cutoff_distance, hint_distance);
210        maximum - dist
211    }
212
213    fn _normalized_distance<Iter1, Iter2>(
214        &self,
215        s1: Iter1,
216        len1: usize,
217        s2: Iter2,
218        len2: usize,
219        score_cutoff: Option<f64>,
220        score_hint: Option<f64>,
221    ) -> f64
222    where
223        Iter1: DoubleEndedIterator + Clone,
224        Iter2: DoubleEndedIterator + Clone,
225        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
226        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
227    {
228        let maximum = self.maximum(len1, len2);
229
230        let cutoff_distance;
231        if let Some(mut cutoff) = score_cutoff {
232            cutoff = cutoff.clamp(0.0, 1.0);
233            cutoff_distance = Some((maximum as f64 * cutoff).ceil() as usize);
234        } else {
235            cutoff_distance = None;
236        }
237
238        let hint_distance;
239        if let Some(mut cutoff) = score_hint {
240            cutoff = cutoff.clamp(0.0, 1.0);
241            hint_distance = Some((maximum as f64 * cutoff).ceil() as usize);
242        } else {
243            hint_distance = None;
244        }
245
246        let dist = self._distance(s1, len1, s2, len2, cutoff_distance, hint_distance);
247        if maximum == 0 {
248            0.0
249        } else {
250            dist as f64 / maximum as f64
251        }
252    }
253
254    fn _normalized_similarity<Iter1, Iter2>(
255        &self,
256        s1: Iter1,
257        len1: usize,
258        s2: Iter2,
259        len2: usize,
260        score_cutoff: Option<f64>,
261        score_hint: Option<f64>,
262    ) -> f64
263    where
264        Iter1: DoubleEndedIterator + Clone,
265        Iter2: DoubleEndedIterator + Clone,
266        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
267        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
268    {
269        let cutoff_score = score_cutoff.map(norm_sim_to_norm_dist);
270        let hint_score = score_hint.map(norm_sim_to_norm_dist);
271
272        let norm_dist = self._normalized_distance(s1, len1, s2, len2, cutoff_score, hint_score);
273        1.0 - norm_dist
274    }
275}
276
277pub trait Metricf64 {
278    fn maximum(&self, len1: usize, len2: usize) -> f64;
279
280    fn _distance<Iter1, Iter2>(
281        &self,
282        s1: Iter1,
283        len1: usize,
284        s2: Iter2,
285        len2: usize,
286        score_cutoff: Option<f64>,
287        score_hint: Option<f64>,
288    ) -> f64
289    where
290        Iter1: DoubleEndedIterator + Clone,
291        Iter2: DoubleEndedIterator + Clone,
292        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
293        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
294    {
295        let maximum = self.maximum(len1, len2);
296
297        let cutoff_similarity = score_cutoff.map(|x| if maximum >= x { maximum - x } else { 0.0 });
298        let hint_similarity = score_hint.map(|x| if maximum >= x { maximum - x } else { 0.0 });
299
300        let sim = self._similarity(s1, len1, s2, len2, cutoff_similarity, hint_similarity);
301        maximum - sim
302    }
303
304    fn _similarity<Iter1, Iter2>(
305        &self,
306        s1: Iter1,
307        len1: usize,
308        s2: Iter2,
309        len2: usize,
310        score_cutoff: Option<f64>,
311        mut score_hint: Option<f64>,
312    ) -> f64
313    where
314        Iter1: DoubleEndedIterator + Clone,
315        Iter2: DoubleEndedIterator + Clone,
316        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
317        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
318    {
319        let maximum = self.maximum(len1, len2);
320        if let Some(cutoff) = score_cutoff {
321            if cutoff > maximum {
322                return maximum;
323            }
324
325            if let Some(hint) = score_hint {
326                score_hint = Some(hint.min(cutoff));
327            }
328        }
329
330        let cutoff_distance = score_cutoff.map(|x| maximum - x);
331        let hint_distance = score_hint.map(|x| maximum - x);
332        let dist = self._distance(s1, len1, s2, len2, cutoff_distance, hint_distance);
333        maximum - dist
334    }
335
336    fn _normalized_distance<Iter1, Iter2>(
337        &self,
338        s1: Iter1,
339        len1: usize,
340        s2: Iter2,
341        len2: usize,
342        score_cutoff: Option<f64>,
343        score_hint: Option<f64>,
344    ) -> f64
345    where
346        Iter1: DoubleEndedIterator + Clone,
347        Iter2: DoubleEndedIterator + Clone,
348        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
349        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
350    {
351        let maximum = self.maximum(len1, len2);
352
353        let cutoff_distance = score_cutoff.map(|x| maximum * x);
354        let hint_distance = score_hint.map(|x| maximum * x);
355
356        let dist = self._distance(s1, len1, s2, len2, cutoff_distance, hint_distance);
357        if maximum > 0.0 {
358            dist / maximum
359        } else {
360            0.0
361        }
362    }
363
364    fn _normalized_similarity<Iter1, Iter2>(
365        &self,
366        s1: Iter1,
367        len1: usize,
368        s2: Iter2,
369        len2: usize,
370        score_cutoff: Option<f64>,
371        score_hint: Option<f64>,
372    ) -> f64
373    where
374        Iter1: DoubleEndedIterator + Clone,
375        Iter2: DoubleEndedIterator + Clone,
376        Iter1::Item: PartialEq<Iter2::Item> + HashableChar + Copy,
377        Iter2::Item: PartialEq<Iter1::Item> + HashableChar + Copy,
378    {
379        let cutoff_score = score_cutoff.map(norm_sim_to_norm_dist);
380        let hint_score = score_hint.map(norm_sim_to_norm_dist);
381
382        let norm_dist = self._normalized_distance(s1, len1, s2, len2, cutoff_score, hint_score);
383        1.0 - norm_dist
384    }
385}