1use std::sync::LazyLock;
16
17use murmur3_32::Murmur3;
18use phf::phf_set;
19use rust_stemmers::{Algorithm, Stemmer};
20
21#[derive(Debug, Clone, PartialEq, Default)]
23pub struct SparseVector {
24 pub indices: Vec<u32>,
26 pub values: Vec<f32>,
28}
29
30pub const DEFAULT_K1: f64 = 1.2;
32pub const DEFAULT_B: f64 = 0.75;
34pub const DEFAULT_AVGDL: f64 = 256.0;
37
38pub fn token_id(token: &str) -> u32 {
41 (Murmur3::hash(0, token.as_bytes()) as i32).unsigned_abs()
42}
43
44static STOPWORDS: phf::Set<&'static str> = phf_set! {
47 "i",
48 "me",
49 "my",
50 "myself",
51 "we",
52 "our",
53 "ours",
54 "ourselves",
55 "you",
56 "you're",
57 "you've",
58 "you'll",
59 "you'd",
60 "your",
61 "yours",
62 "yourself",
63 "yourselves",
64 "he",
65 "him",
66 "his",
67 "himself",
68 "she",
69 "she's",
70 "her",
71 "hers",
72 "herself",
73 "it",
74 "it's",
75 "its",
76 "itself",
77 "they",
78 "them",
79 "their",
80 "theirs",
81 "themselves",
82 "what",
83 "which",
84 "who",
85 "whom",
86 "this",
87 "that",
88 "that'll",
89 "these",
90 "those",
91 "am",
92 "is",
93 "are",
94 "was",
95 "were",
96 "be",
97 "been",
98 "being",
99 "have",
100 "has",
101 "had",
102 "having",
103 "do",
104 "does",
105 "did",
106 "doing",
107 "a",
108 "an",
109 "the",
110 "and",
111 "but",
112 "if",
113 "or",
114 "because",
115 "as",
116 "until",
117 "while",
118 "of",
119 "at",
120 "by",
121 "for",
122 "with",
123 "about",
124 "against",
125 "between",
126 "into",
127 "through",
128 "during",
129 "before",
130 "after",
131 "above",
132 "below",
133 "to",
134 "from",
135 "up",
136 "down",
137 "in",
138 "out",
139 "on",
140 "off",
141 "over",
142 "under",
143 "again",
144 "further",
145 "then",
146 "once",
147 "here",
148 "there",
149 "when",
150 "where",
151 "why",
152 "how",
153 "all",
154 "any",
155 "both",
156 "each",
157 "few",
158 "more",
159 "most",
160 "other",
161 "some",
162 "such",
163 "no",
164 "nor",
165 "not",
166 "only",
167 "own",
168 "same",
169 "so",
170 "than",
171 "too",
172 "very",
173 "s",
174 "t",
175 "can",
176 "will",
177 "just",
178 "don",
179 "don't",
180 "should",
181 "should've",
182 "now",
183 "d",
184 "ll",
185 "m",
186 "o",
187 "re",
188 "ve",
189 "y",
190 "ain",
191 "aren",
192 "aren't",
193 "couldn",
194 "couldn't",
195 "didn",
196 "didn't",
197 "doesn",
198 "doesn't",
199 "hadn",
200 "hadn't",
201 "hasn",
202 "hasn't",
203 "haven",
204 "haven't",
205 "isn",
206 "isn't",
207 "ma",
208 "mightn",
209 "mightn't",
210 "mustn",
211 "mustn't",
212 "needn",
213 "needn't",
214 "shan",
215 "shan't",
216 "shouldn",
217 "shouldn't",
218 "wasn",
219 "wasn't",
220 "weren",
221 "weren't",
222 "won",
223 "won't",
224 "wouldn",
225 "wouldn't",
226};
227
228static STEMMER: LazyLock<Stemmer> = LazyLock::new(|| Stemmer::create(Algorithm::English));
229
230#[inline]
231fn process_token<F>(raw: &str, buf: &mut [u8; 64], f: &mut F)
232where
233 F: FnMut(&str),
234{
235 let bytes = raw.as_bytes();
236 let len = bytes.len();
237 if len <= buf.len() && raw.is_ascii() {
238 for (j, &b) in bytes.iter().enumerate() {
239 buf[j] = b.to_ascii_lowercase();
240 }
241 let lower =
246 std::str::from_utf8(&buf[..len]).expect("ascii lowercasing preserves valid UTF-8");
247 if !STOPWORDS.contains(lower) {
248 let stemmed = STEMMER.stem(lower);
249 f(&stemmed);
250 }
251 } else {
252 let lower = raw.to_lowercase();
253 if !STOPWORDS.contains(lower.as_str()) {
254 let stemmed = STEMMER.stem(&lower);
255 f(&stemmed);
256 }
257 }
258}
259
260#[inline]
262pub fn for_each_token<F>(text: &str, mut f: F)
263where
264 F: FnMut(&str),
265{
266 let mut buf = [0u8; 64];
267
268 if text.is_ascii() {
269 let bytes = text.as_bytes();
270 let mut start = None;
271 for (i, &b) in bytes.iter().enumerate() {
272 if b.is_ascii_alphanumeric() {
273 if start.is_none() {
274 start = Some(i);
275 }
276 } else if let Some(s) = start {
277 process_token(&text[s..i], &mut buf, &mut f);
278 start = None;
279 }
280 }
281 if let Some(s) = start {
282 process_token(&text[s..], &mut buf, &mut f);
283 }
284 } else {
285 let mut start = None;
286 for (i, c) in text.char_indices() {
287 if c.is_alphanumeric() {
288 if start.is_none() {
289 start = Some(i);
290 }
291 } else if let Some(s) = start {
292 process_token(&text[s..i], &mut buf, &mut f);
293 start = None;
294 }
295 }
296 if let Some(s) = start {
297 process_token(&text[s..], &mut buf, &mut f);
298 }
299 }
300}
301
302#[inline]
304pub fn for_each_token_id<F>(text: &str, mut f: F)
305where
306 F: FnMut(u32),
307{
308 for_each_token(text, |token| {
309 f(token_id(token));
310 });
311}
312
313pub fn tokenize(text: &str) -> Vec<String> {
319 let mut tokens = Vec::new();
320 for_each_token(text, |token| {
321 tokens.push(token.to_string());
322 });
323 tokens
324}
325
326pub fn embed_query(text: &str) -> SparseVector {
329 let mut indices: Vec<u32> = Vec::with_capacity(text.len() / 6 + 1);
330 for_each_token_id(text, |id| {
331 indices.push(id);
332 });
333
334 if indices.is_empty() {
335 return SparseVector::default();
336 }
337
338 indices.sort_unstable();
339 indices.dedup();
340
341 let values = vec![1.0; indices.len()];
342 SparseVector { indices, values }
343}
344
345pub fn embed_document(text: &str) -> SparseVector {
348 embed_document_with(text, DEFAULT_K1, DEFAULT_B, DEFAULT_AVGDL)
349}
350
351pub fn embed_document_with(text: &str, k1: f64, b: f64, avgdl: f64) -> SparseVector {
360 let mut token_ids: Vec<u32> = Vec::with_capacity(text.len() / 6 + 1);
361 for_each_token_id(text, |id| {
362 token_ids.push(id);
363 });
364
365 if token_ids.is_empty() {
366 return SparseVector::default();
367 }
368
369 let doc_len = token_ids.len() as f64;
370 let safe_avgdl = if avgdl.is_finite() && avgdl > 0.0 {
371 avgdl
372 } else {
373 DEFAULT_AVGDL
374 };
375 let denom_scale = k1 * (1.0 - b + b * doc_len / safe_avgdl);
376 let k1p1 = k1 + 1.0;
377
378 token_ids.sort_unstable();
379
380 let mut indices = Vec::with_capacity(token_ids.len());
381 let mut values = Vec::with_capacity(token_ids.len());
382
383 let mut i = 0;
384 while i < token_ids.len() {
385 let id = token_ids[i];
386 let mut count = 1u32;
387 while i + 1 < token_ids.len() && token_ids[i + 1] == id {
388 count += 1;
389 i += 1;
390 }
391 indices.push(id);
392 let n = count as f64;
393 values.push((n * k1p1 / (denom_scale + n)) as f32);
394 i += 1;
395 }
396
397 SparseVector { indices, values }
398}