Skip to content

Commit c2dc9c6

Browse files
authored
Improve Levenshtein search speed and index cleanup (#19)
1 parent c00de1c commit c2dc9c6

2 files changed

Lines changed: 41 additions & 9 deletions

File tree

src/lib.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,20 @@ where
222222
let len = max(token.len(), pattern_token.len()) as f64;
223223
// calculate k (based on the threshold) to bound the Levenshtein distance
224224
let k = ((1.0 - self.option.threshold) * len).ceil() as u32;
225-
// levenshtein_simd_k only works on ASCII byte slices, so the token strings
226-
// are directly treated as byte slices
227-
match levenshtein_simd_k(token.as_bytes(), pattern_token.as_bytes(), k) {
228-
Some(dist) => 1.0 - if len == 0.0 { 0.0 } else { (dist as f64) / len },
229-
None => f64::MIN,
225+
226+
// cheap prefilter: if length difference already exceeds k,
227+
// levenshtein_simd_k would return None, so skip calling it.
228+
let tlen = token.len() as i32;
229+
let plen = pattern_token.len() as i32;
230+
if ((tlen - plen).abs() as u32) > k {
231+
f64::MIN
232+
} else {
233+
// levenshtein_simd_k only works on ASCII byte slices, so the token strings
234+
// are directly treated as byte slices
235+
match levenshtein_simd_k(token.as_bytes(), pattern_token.as_bytes(), k) {
236+
Some(dist) => 1.0 - if len == 0.0 { 0.0 } else { (dist as f64) / len },
237+
None => f64::MIN,
238+
}
230239
}
231240
} else {
232241
jaro_winkler(token, &pattern_token)
@@ -276,10 +285,13 @@ where
276285
pub fn remove(&mut self, id: &Id) {
277286
if let Some(id_num) = self.ids_map.get(id) {
278287
for token in &self.forward_map[id_num] {
279-
self.reverse_map
280-
.get_mut(token)
281-
.unwrap()
282-
.retain(|i| i != id_num);
288+
if let Some(vec) = self.reverse_map.get_mut(token) {
289+
vec.retain(|i| i != id_num);
290+
if vec.is_empty() {
291+
// prune empty token entry to keep the index small
292+
self.reverse_map.remove(token);
293+
}
294+
}
283295
}
284296
self.forward_map.remove(id_num);
285297
self.reverse_ids_map.remove(id_num);
@@ -328,6 +340,8 @@ where
328340

329341
tokens.retain(|token| !token.is_empty());
330342

343+
344+
331345
tokens
332346
}
333347
}

tests/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,21 @@ fn populate_engine() -> SimSearch<String> {
2727
fn test_quickcheck(tokens: Vec<String>) {
2828
ENGINE.search(&tokens.join(" "));
2929
}
30+
31+
32+
#[test]
33+
fn remove_prunes_reverse_map_entries() {
34+
let mut engine: SimSearch<String> = SimSearch::new();
35+
let id = "id1".to_string();
36+
37+
engine.insert(id.clone(), "unique-token");
38+
// ensure present
39+
let res = engine.search("unique-token");
40+
assert_eq!(res, vec![id.clone()]);
41+
42+
engine.remove(&id);
43+
44+
// after removal the token should no longer be found
45+
let res2: Vec<String> = engine.search("unique-token");
46+
assert!(res2.is_empty());
47+
}

0 commit comments

Comments
 (0)