Find terms in the index that fuzzy-match the query term. Returns `(matched_term, edit_distance)` pairs for all terms within the adaptive distance threshold. Results are sorted by distance (closest matches first).
(
query_term: &str,
index_terms: impl Iterator<Item = &'a str>,
)
| 73 | /// the adaptive distance threshold. Results are sorted by distance |
| 74 | /// (closest matches first). |
| 75 | pub fn fuzzy_match<'a>( |
| 76 | query_term: &str, |
| 77 | index_terms: impl Iterator<Item = &'a str>, |
| 78 | ) -> Vec<(&'a str, usize)> { |
| 79 | let q_char_len = query_term.chars().count(); |
| 80 | let max_dist = max_distance_for_length(q_char_len); |
| 81 | if max_dist == 0 { |
| 82 | return Vec::new(); |
| 83 | } |
| 84 | |
| 85 | // Length-bucket prefilter: group candidates by character length, then |
| 86 | // visit only buckets within the distance window. Edit distance is at |
| 87 | // least |len(a) - len(b)|, so terms outside [q-d .. q+d] cannot match. |
| 88 | // This bounds the number of expensive O(L²) levenshtein calls to the |
| 89 | // candidates in the relevant buckets, avoiding whole-dictionary scans |
| 90 | // on large term indexes. |
| 91 | use std::collections::HashMap; |
| 92 | let mut buckets: HashMap<usize, Vec<&'a str>> = HashMap::new(); |
| 93 | for term in index_terms { |
| 94 | buckets.entry(term.chars().count()).or_default().push(term); |
| 95 | } |
| 96 | |
| 97 | let low = q_char_len.saturating_sub(max_dist); |
| 98 | let high = q_char_len.saturating_add(max_dist); |
| 99 | let mut matches: Vec<(&'a str, usize)> = Vec::new(); |
| 100 | for len in low..=high { |
| 101 | let Some(bucket) = buckets.get(&len) else { |
| 102 | continue; |
| 103 | }; |
| 104 | for term in bucket { |
| 105 | let dist = levenshtein(query_term, term); |
| 106 | if dist > 0 && dist <= max_dist { |
| 107 | matches.push((*term, dist)); |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | matches.sort_by_key(|&(_, d)| d); |
| 113 | matches |
| 114 | } |
| 115 | |
| 116 | /// Score discount for fuzzy matches based on edit distance. |
| 117 | /// |