1use crate::fast_hash::hash_bytes;
7
8#[derive(Debug, Clone)]
12pub struct ShingleIterator<'a> {
13 data: &'a [u8],
15 k: usize,
17 pos: usize,
19}
20
21impl<'a> ShingleIterator<'a> {
22 pub fn new(data: &'a [u8], k: usize) -> Self {
28 Self { data, k: k.max(1), pos: 0 }
29 }
30
31 pub fn from_str(s: &'a str, k: usize) -> Self {
35 Self::new(s.as_bytes(), k)
36 }
37
38 #[must_use]
40 pub fn count_shingles(&self) -> usize {
41 if self.data.len() < self.k {
42 return 0;
43 }
44 self.data.len().saturating_sub(self.k).saturating_add(1)
45 }
46
47 #[must_use]
49 pub fn is_empty(&self) -> bool {
50 self.count_shingles() == 0
51 }
52}
53
54impl<'a> Iterator for ShingleIterator<'a> {
55 type Item = &'a [u8];
56
57 fn next(&mut self) -> Option<Self::Item> {
58 let end = self.pos.checked_add(self.k)?;
59 if end > self.data.len() {
60 return None;
61 }
62
63 let shingle = &self.data[self.pos..end];
64 self.pos += 1;
65 Some(shingle)
66 }
67
68 fn size_hint(&self) -> (usize, Option<usize>) {
69 let remaining = self.count_shingles().saturating_sub(self.pos);
70 (remaining, Some(remaining))
71 }
72}
73
74impl ExactSizeIterator for ShingleIterator<'_> {}
75
76#[derive(Debug, Clone)]
80pub struct HashedShingleIterator<'a> {
81 inner: ShingleIterator<'a>,
82}
83
84impl<'a> HashedShingleIterator<'a> {
85 pub fn new(data: &'a [u8], k: usize) -> Self {
87 Self {
88 inner: ShingleIterator::new(data, k),
89 }
90 }
91
92 #[allow(dead_code)]
94 pub fn from_str(s: &'a str, k: usize) -> Self {
95 Self::new(s.as_bytes(), k)
96 }
97}
98
99impl Iterator for HashedShingleIterator<'_> {
100 type Item = u64;
101
102 fn next(&mut self) -> Option<Self::Item> {
103 self.inner.next().map(hash_bytes)
104 }
105
106 fn size_hint(&self) -> (usize, Option<usize>) {
107 self.inner.size_hint()
108 }
109}
110
111impl ExactSizeIterator for HashedShingleIterator<'_> {}
112
113#[must_use]
115#[allow(dead_code)]
116pub fn get_shingles(data: &[u8], k: usize) -> Vec<&[u8]> {
117 ShingleIterator::new(data, k).collect()
118}
119
120#[must_use]
122#[allow(dead_code)]
123pub fn get_hashed_shingles(data: &[u8], k: usize) -> Vec<u64> {
124 HashedShingleIterator::new(data, k).collect()
125}
126
127#[must_use]
132#[allow(dead_code)]
133pub fn normalize_text(text: &str) -> String {
134 let mut result = String::with_capacity(text.len());
135 let mut prev_was_space = true; for c in text.chars() {
138 if c.is_whitespace() {
139 if !prev_was_space {
140 result.push(' ');
141 prev_was_space = true;
142 }
143 } else {
144 for lc in c.to_lowercase() {
146 result.push(lc);
147 }
148 prev_was_space = false;
149 }
150 }
151
152 if result.ends_with(' ') {
154 result.pop();
155 }
156
157 result
158}
159
160#[allow(dead_code)]
165pub struct WordShingleIterator<'a> {
166 words: Vec<&'a str>,
168 k: usize,
170 pos: usize,
172}
173
174impl<'a> WordShingleIterator<'a> {
175 #[allow(dead_code)]
181 pub fn new(text: &'a str, k: usize) -> Self {
182 let k = k.max(1);
183 let words: Vec<&str> = text.split_whitespace().collect();
184 Self { words, k, pos: 0 }
185 }
186
187 #[must_use]
189 #[allow(dead_code)]
190 pub fn count_shingles(&self) -> usize {
191 if self.words.len() < self.k {
192 return 0;
193 }
194 self.words.len().saturating_sub(self.k).saturating_add(1)
195 }
196}
197
198impl<'a> Iterator for WordShingleIterator<'a> {
199 type Item = Vec<&'a str>;
200
201 fn next(&mut self) -> Option<Self::Item> {
202 let end = self.pos.checked_add(self.k)?;
203 if end > self.words.len() {
204 return None;
205 }
206
207 let shingle: Vec<&str> = self.words[self.pos..end].to_vec();
208 self.pos += 1;
209 Some(shingle)
210 }
211
212 fn size_hint(&self) -> (usize, Option<usize>) {
213 let remaining = self.count_shingles().saturating_sub(self.pos);
214 (remaining, Some(remaining))
215 }
216}
217
218impl ExactSizeIterator for WordShingleIterator<'_> {}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn shingle_iterator_basic() {
229 let data = b"hello";
230 let shingles: Vec<_> = ShingleIterator::new(data, 2).collect();
231
232 assert_eq!(shingles.len(), 4);
233 assert_eq!(shingles[0], b"he");
234 assert_eq!(shingles[1], b"el");
235 assert_eq!(shingles[2], b"ll");
236 assert_eq!(shingles[3], b"lo");
237 }
238
239 #[test]
240 fn shingle_iterator_single_byte() {
241 let data = b"abc";
242 let shingles: Vec<_> = ShingleIterator::new(data, 1).collect();
243
244 assert_eq!(shingles.len(), 3);
245 assert_eq!(shingles[0], b"a");
246 assert_eq!(shingles[1], b"b");
247 assert_eq!(shingles[2], b"c");
248 }
249
250 #[test]
251 fn shingle_iterator_short_input() {
252 let data = b"hi";
253 let shingles: Vec<_> = ShingleIterator::new(data, 5).collect();
254 assert!(shingles.is_empty());
255 }
256
257 #[test]
258 fn shingle_count_correct() {
259 let iter = ShingleIterator::new(b"hello world", 3);
260 assert_eq!(iter.count_shingles(), 9); }
262
263 #[test]
264 fn exact_size_iterator() {
265 let iter = ShingleIterator::new(b"hello", 2);
266 let (low, high) = iter.size_hint();
267 assert_eq!(low, 4);
268 assert_eq!(high, Some(4));
269 }
270
271 #[test]
272 fn hashed_shingle_iterator() {
273 let data = b"test";
274 let hashes: Vec<_> = HashedShingleIterator::new(data, 2).collect();
275
276 assert_eq!(hashes.len(), 3);
277 let hashes2: Vec<_> = HashedShingleIterator::new(data, 2).collect();
279 assert_eq!(hashes, hashes2);
280 }
281
282 #[test]
283 fn from_str_works() {
284 let s = "hello";
285 let shingles: Vec<_> = ShingleIterator::from_str(s, 2).collect();
286 assert_eq!(shingles[0], b"he");
287 }
288
289 #[test]
290 fn get_shingles_helper() {
291 let shingles = get_shingles(b"abcd", 2);
292 assert_eq!(shingles.len(), 3);
293 }
294
295 #[test]
296 fn normalize_text_basic() {
297 assert_eq!(normalize_text("Hello World"), "hello world");
298 assert_eq!(normalize_text(" multiple spaces "), "multiple spaces");
299 }
300
301 #[test]
302 fn normalize_text_unicode() {
303 assert_eq!(normalize_text("CAFÉ"), "café");
304 assert_eq!(normalize_text("Hello 世界"), "hello 世界");
305 }
306
307 #[test]
308 fn word_shingle_iterator() {
309 let text = "the quick brown fox";
310 let shingles: Vec<_> = WordShingleIterator::new(text, 2).collect();
311
312 assert_eq!(shingles.len(), 3);
313 assert_eq!(shingles[0], vec!["the", "quick"]);
314 assert_eq!(shingles[1], vec!["quick", "brown"]);
315 assert_eq!(shingles[2], vec!["brown", "fox"]);
316 }
317
318 #[test]
319 fn word_shingle_empty() {
320 let shingles: Vec<_> = WordShingleIterator::new("", 2).collect();
321 assert!(shingles.is_empty());
322 }
323
324 #[test]
325 fn shingle_iterator_is_empty() {
326 let iter = ShingleIterator::new(b"hi", 5);
327 assert!(iter.is_empty());
328
329 let iter = ShingleIterator::new(b"hello", 2);
330 assert!(!iter.is_empty());
331 }
332
333 #[test]
334 fn large_k_works() {
335 let data = vec![b'a'; 1000];
336 let shingles: Vec<_> = ShingleIterator::new(&data, 100).collect();
337 assert_eq!(shingles.len(), 901);
338 }
339}