Skip to content

Commit 488ed51

Browse files
committed
feat(bindings): Added strings method for getting all stored strings & more search improvements
1 parent b7a82f7 commit 488ed51

15 files changed

Lines changed: 127 additions & 146 deletions

File tree

src/database/hashdb.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,7 @@ impl HashDb {
5454
impl Database for HashDb {
5555
fn insert(&mut self, text: String) {
5656
let mut interner = self.interner.lock().unwrap();
57-
let mut features = self.feature_extractor.features(&text, &mut interner);
58-
// FIX: Hmm append_feature_counts does counts. Should this sorting be moved there and make
59-
// dedup redundant?
60-
features.sort_unstable();
61-
features.dedup();
57+
let features = self.feature_extractor.features(&text, &mut interner);
6258
let size = features.len();
6359
let string_id = self.strings.len();
6460

@@ -102,4 +98,8 @@ impl Database for HashDb {
10298
fn interner(&self) -> Arc<Mutex<Rodeo>> {
10399
Arc::clone(&self.interner)
104100
}
101+
102+
fn total_strings(&self) -> usize {
103+
self.strings.len()
104+
}
105105
}

src/database/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
mod hashdb;
22

33
use crate::extractors::FeatureExtractor;
4-
use rustc_hash::FxHashSet;
54
use lasso::{Rodeo, Spur};
5+
use rustc_hash::FxHashSet;
66
use std::sync::{Arc, Mutex};
77

88
pub type StringId = usize;
@@ -16,6 +16,7 @@ pub trait Database: Send + Sync {
1616
fn feature_extractor(&self) -> &dyn FeatureExtractor;
1717
fn max_feature_len(&self) -> usize;
1818
fn interner(&self) -> Arc<Mutex<Rodeo>>;
19+
fn total_strings(&self) -> usize;
1920
}
2021

2122
pub use hashdb::HashDb;

src/extractors/character_ngrams.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,34 @@ impl FeatureExtractor for CharacterNgrams {
2828
return vec![];
2929
}
3030

31-
let mut ngrams = Vec::new();
32-
let padding = self.endmarker.repeat(self.n.saturating_sub(1));
31+
// Pre-calculate capacity to avoid reallocations
32+
let text_len = text.chars().count();
33+
let padding_len = self.n.saturating_sub(1);
34+
let total_len = text_len + 2 * padding_len;
3335

34-
// Create an iterator that includes padding
35-
let padded_text_iter = padding.chars().chain(text.chars()).chain(padding.chars());
36+
if total_len < self.n {
37+
return vec![];
38+
}
39+
40+
let expected_ngrams = total_len - self.n + 1;
41+
let mut ngrams = Vec::with_capacity(expected_ngrams);
42+
43+
let padding = self.endmarker.repeat(padding_len);
3644

37-
// Use a buffer to collect characters for each n-gram
38-
let mut buffer: Vec<char> = Vec::with_capacity(self.n);
45+
// collect chars once, then slice
46+
let mut all_chars = Vec::with_capacity(total_len);
47+
all_chars.extend(padding.chars());
48+
all_chars.extend(text.chars());
49+
all_chars.extend(padding.chars());
3950

40-
for ch in padded_text_iter {
41-
buffer.push(ch);
42-
if buffer.len() == self.n {
43-
ngrams.push(buffer.iter().collect::<String>());
44-
buffer.remove(0);
51+
// Generate n-grams using efficient windowing
52+
for window in all_chars.windows(self.n) {
53+
// Pre-allocate string with known capacity
54+
let mut ngram = String::with_capacity(self.n * 4); // Assume max 4 bytes per char
55+
for &ch in window {
56+
ngram.push(ch);
4557
}
58+
ngrams.push(ngram);
4659
}
4760

4861
super::append_feature_counts(interner, ngrams)

src/extractors/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
mod character_ngrams;
22
mod word_ngrams;
33

4-
use rustc_hash::FxHashMap;
54
use lasso::{Rodeo, Spur};
5+
use rustc_hash::FxHashMap;
6+
use std::fmt::Write;
67

78
/// Takes a list of features and makes each one unique by appending its occurrence count,
8-
/// then interns the result.
9+
/// then interns the result and returns them sorted.
910
fn append_feature_counts(interner: &mut Rodeo, features: Vec<String>) -> Vec<Spur> {
1011
let mut counter: FxHashMap<String, usize> = FxHashMap::default();
1112
let mut unique_features = Vec::with_capacity(features.len());
13+
1214
for val in features {
1315
let count = counter.entry(val.clone()).or_insert(0);
1416
*count += 1;
15-
let unique_string = format!("{}{}", val, *count);
17+
18+
let mut unique_string = String::with_capacity(val.len() + 8); // Extra space for count
19+
unique_string.push_str(&val);
20+
write!(&mut unique_string, "{count}",).unwrap();
21+
1622
unique_features.push(interner.get_or_intern(unique_string));
1723
}
24+
25+
unique_features.sort_unstable();
1826
unique_features
1927
}
2028

src/extractors/word_ngrams.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl FeatureExtractor for WordNgrams {
3232

3333
let tokens = text.split(&self.splitter).filter(|s| !s.is_empty());
3434

35-
// Create an iterator that includes padding
35+
// an iterator that includes padding
3636
let padded_tokens_iter = std::iter::once(self.padder.as_str())
3737
.chain(tokens)
3838
.chain(std::iter::once(self.padder.as_str()));

src/measures/cosine.rs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use super::Measure;
1+
use super::{compute_intersection_size, Measure};
22
use crate::database::Database;
3-
43
use lasso::Spur;
54

65
#[derive(Default, Clone, Copy)]
@@ -28,25 +27,10 @@ impl Measure for Cosine {
2827
return 0.0;
2928
}
3029

31-
let mut intersection_size = 0;
32-
let mut i = 0;
33-
let mut j = 0;
34-
35-
while i < x.len() && j < y.len() {
36-
if x[i] == y[j] {
37-
intersection_size += 1;
38-
i += 1;
39-
j += 1;
40-
} else if x[i] < y[j] {
41-
i += 1;
42-
} else {
43-
j += 1;
44-
}
45-
}
46-
30+
let intersection_size = compute_intersection_size(x, y);
4731
let denominator = (x.len() as f64 * y.len() as f64).sqrt();
4832

49-
if denominator == 0.0 {
33+
if denominator == 0.0 || !denominator.is_finite() {
5034
0.0
5135
} else {
5236
intersection_size as f64 / denominator

src/measures/dice.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use super::Measure;
1+
use super::{compute_intersection_size, Measure};
22
use crate::database::Database;
3-
43
use lasso::Spur;
54

65
#[derive(Default, Clone, Copy)]
@@ -34,22 +33,7 @@ impl Measure for Dice {
3433
return 0.0;
3534
}
3635

37-
let mut intersection_size = 0;
38-
let mut i = 0;
39-
let mut j = 0;
40-
41-
while i < x.len() && j < y.len() {
42-
if x[i] == y[j] {
43-
intersection_size += 1;
44-
i += 1;
45-
j += 1;
46-
} else if x[i] < y[j] {
47-
i += 1;
48-
} else {
49-
j += 1;
50-
}
51-
}
52-
36+
let intersection_size = compute_intersection_size(x, y);
5337
let denominator = (x.len() + y.len()) as f64;
5438

5539
if denominator == 0.0 {

src/measures/exact_match.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use super::Measure;
22
use crate::database::Database;
3-
43
use lasso::Spur;
54

65
#[derive(Default, Clone, Copy)]
@@ -36,4 +35,3 @@ impl Measure for ExactMatch {
3635
}
3736
}
3837
}
39-

src/measures/jaccard.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use super::Measure;
1+
use super::{compute_intersection_size, Measure};
22
use crate::database::Database;
3-
43
use lasso::Spur;
54

65
#[derive(Default, Clone, Copy)]
@@ -30,22 +29,7 @@ impl Measure for Jaccard {
3029
return 0.0;
3130
}
3231

33-
let mut intersection_size = 0;
34-
let mut i = 0;
35-
let mut j = 0;
36-
37-
while i < x.len() && j < y.len() {
38-
if x[i] == y[j] {
39-
intersection_size += 1;
40-
i += 1;
41-
j += 1;
42-
} else if x[i] < y[j] {
43-
i += 1;
44-
} else {
45-
j += 1;
46-
}
47-
}
48-
32+
let intersection_size = compute_intersection_size(x, y);
4933
let union_size = (x.len() + y.len() - intersection_size) as f64;
5034

5135
if union_size == 0.0 {

src/measures/mod.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,26 @@ pub trait Measure: Send + Sync {
1515
fn similarity(&self, x: &[Spur], y: &[Spur]) -> f64;
1616
}
1717

18-
// FIX: All measures use the same intersection logic so this function can clean up measures module:
19-
//fn compute_intersection_size(x: &[Spur], y: &[Spur]) -> usize {
20-
// let mut intersection_size = 0;
21-
// let mut i = 0;
22-
// let mut j = 0;
23-
//
24-
// while i < x.len() && j < y.len() {
25-
// match x[i].cmp(&y[j]) {
26-
// std::cmp::Ordering::Equal => {
27-
// intersection_size += 1;
28-
// i += 1;
29-
// j += 1;
30-
// }
31-
// std::cmp::Ordering::Less => i += 1,
32-
// std::cmp::Ordering::Greater => j += 1,
33-
// }
34-
// }
35-
//
36-
// intersection_size
37-
//}
18+
// Helper function which computes the number of intersections between to vec of Spur
19+
pub(crate) fn compute_intersection_size(x: &[Spur], y: &[Spur]) -> usize {
20+
let mut intersection_size = 0;
21+
let mut i = 0;
22+
let mut j = 0;
23+
24+
while i < x.len() && j < y.len() {
25+
match x[i].cmp(&y[j]) {
26+
std::cmp::Ordering::Equal => {
27+
intersection_size += 1;
28+
i += 1;
29+
j += 1;
30+
}
31+
std::cmp::Ordering::Less => i += 1,
32+
std::cmp::Ordering::Greater => j += 1,
33+
}
34+
}
35+
36+
intersection_size
37+
}
3838
pub use cosine::Cosine;
3939
pub use dice::Dice;
4040
pub use exact_match::ExactMatch;

0 commit comments

Comments
 (0)