usearch 2.9.1 → 2.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CITATION.cff +1 -1
  2. package/CONTRIBUTING.md +10 -2
  3. package/Cargo.toml +1 -1
  4. package/README.md +12 -15
  5. package/VERSION +1 -1
  6. package/c/test.c +58 -50
  7. package/conanfile.py +1 -1
  8. package/include/usearch/index.hpp +1 -1
  9. package/include/usearch/index_plugins.hpp +20 -10
  10. package/javascript/types/usearch.d.ts.map +1 -1
  11. package/javascript/usearch.js +4 -1
  12. package/package.json +1 -1
  13. package/simsimd/CONTRIBUTING.md +88 -0
  14. package/simsimd/Cargo.lock +4 -3
  15. package/simsimd/Cargo.toml +10 -2
  16. package/simsimd/README.md +102 -60
  17. package/simsimd/include/simsimd/binary.h +44 -0
  18. package/simsimd/include/simsimd/complex.h +174 -0
  19. package/simsimd/include/simsimd/simsimd.h +18 -9
  20. package/simsimd/include/simsimd/spatial.h +128 -36
  21. package/simsimd/include/simsimd/types.h +3 -2
  22. package/simsimd/package-lock.json +5 -5
  23. package/simsimd/package.json +1 -1
  24. package/simsimd/rust/benches/native.rs +12 -9
  25. package/simsimd/rust/lib.c +32 -55
  26. package/simsimd/rust/lib.rs +374 -56
  27. package/stringzilla/CONTRIBUTING.md +17 -0
  28. package/stringzilla/Cargo.lock +1 -1
  29. package/stringzilla/Cargo.toml +1 -1
  30. package/stringzilla/README.md +175 -34
  31. package/stringzilla/VERSION +1 -1
  32. package/stringzilla/build.rs +77 -4
  33. package/stringzilla/c/lib.c +33 -0
  34. package/stringzilla/cli/README.md +30 -13
  35. package/stringzilla/cli/__init__.py +0 -0
  36. package/stringzilla/cli/split.py +66 -33
  37. package/stringzilla/cli/wc.py +101 -21
  38. package/stringzilla/include/stringzilla/experimental.h +122 -1
  39. package/stringzilla/include/stringzilla/stringzilla.h +262 -132
  40. package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
  41. package/stringzilla/package.json +1 -1
  42. package/stringzilla/rust/lib.rs +673 -162
  43. package/wasmer.toml +1 -1
@@ -1,111 +1,134 @@
1
1
  #![cfg_attr(not(test), no_std)]
2
2
 
3
- use core::ffi::c_void;
4
-
5
- // Import the functions from the StringZilla C library.
6
- extern "C" {
7
- fn sz_find(
8
- haystack: *const c_void,
9
- haystack_length: usize,
10
- needle: *const c_void,
11
- needle_length: usize,
12
- ) -> *const c_void;
13
-
14
- fn sz_rfind(
15
- haystack: *const c_void,
16
- haystack_length: usize,
17
- needle: *const c_void,
18
- needle_length: usize,
19
- ) -> *const c_void;
20
-
21
- fn sz_find_char_from(
22
- haystack: *const c_void,
23
- haystack_length: usize,
24
- needle: *const c_void,
25
- needle_length: usize,
26
- ) -> *const c_void;
27
-
28
- fn sz_rfind_char_from(
29
- haystack: *const c_void,
30
- haystack_length: usize,
31
- needle: *const c_void,
32
- needle_length: usize,
33
- ) -> *const c_void;
34
-
35
- fn sz_find_char_not_from(
36
- haystack: *const c_void,
37
- haystack_length: usize,
38
- needle: *const c_void,
39
- needle_length: usize,
40
- ) -> *const c_void;
41
-
42
- fn sz_rfind_char_not_from(
43
- haystack: *const c_void,
44
- haystack_length: usize,
45
- needle: *const c_void,
46
- needle_length: usize,
47
- ) -> *const c_void;
48
-
49
- fn sz_edit_distance(
50
- haystack1: *const c_void,
51
- haystack1_length: usize,
52
- haystack2: *const c_void,
53
- haystack2_length: usize,
54
- bound: usize,
55
- allocator: *const c_void,
56
- ) -> usize;
57
-
58
- fn sz_alignment_score(
59
- haystack1: *const c_void,
60
- haystack1_length: usize,
61
- haystack2: *const c_void,
62
- haystack2_length: usize,
63
- matrix: *const c_void,
64
- gap: i8,
65
- allocator: *const c_void,
66
- ) -> isize;
67
- }
68
-
69
- /// The [StringZilla] trait provides a collection of string searching and manipulation functionalities.
70
- pub trait StringZilla<N>
71
- where
72
- N: AsRef<[u8]>,
73
- {
74
- /// Locates first matching substring. Equivalent to `memmem(haystack, h_length, needle, n_length)` in LibC. Similar
75
- /// to `strstr(haystack, needle)` in LibC, but requires known length.
76
- fn sz_find(&self, needle: N) -> Option<usize>;
77
-
78
- /// Locates the last matching substring.
79
- fn sz_rfind(&self, needle: N) -> Option<usize>;
80
-
81
- /// Finds the first character in the haystack, that is present in the needle.
82
- fn sz_find_char_from(&self, needles: N) -> Option<usize>;
83
-
84
- /// Finds the last character in the haystack, that is present in the needle.
85
- fn sz_rfind_char_from(&self, needles: N) -> Option<usize>;
86
-
87
- /// Finds the first character in the haystack, that is __not__ present in the needle.
88
- fn sz_find_char_not_from(&self, needles: N) -> Option<usize>;
89
-
90
- /// Finds the last character in the haystack, that is __not__ present in the needle.
91
- fn sz_rfind_char_not_from(&self, needles: N) -> Option<usize>;
92
-
93
- /// Computes the Levenshtein edit-distance between two strings using the Wagner-Fisher algorithm.
94
- /// Similar to the Needleman-Wunsch alignment algorithm. Often used in fuzzy string matching.
95
- fn sz_edit_distance(&self, needle: N) -> usize;
96
-
97
- /// Computes Needleman–Wunsch alignment score for two strings. Often used in bioinformatics and cheminformatics.
98
- /// Similar to the Levenshtein edit-distance, parameterized for gap and substitution penalties.
99
- fn sz_alignment_score(&self, needle: N, matrix: [[i8; 256]; 256], gap: i8) -> isize;
100
- }
3
+ /// The `sz` module provides a collection of string searching and manipulation functionality,
4
+ /// designed for high efficiency and compatibility with no_std environments. This module offers
5
+ /// various utilities for byte string manipulation, including search, reverse search, and
6
+ /// edit-distance calculations, suitable for a wide range of applications from basic string
7
+ /// processing to complex text analysis tasks.
8
+
9
+ pub mod sz {
10
+
11
+ use core::ffi::c_void;
12
+
13
+ // Import the functions from the StringZilla C library.
14
+ extern "C" {
15
+ fn sz_find(
16
+ haystack: *const c_void,
17
+ haystack_length: usize,
18
+ needle: *const c_void,
19
+ needle_length: usize,
20
+ ) -> *const c_void;
21
+
22
+ fn sz_rfind(
23
+ haystack: *const c_void,
24
+ haystack_length: usize,
25
+ needle: *const c_void,
26
+ needle_length: usize,
27
+ ) -> *const c_void;
28
+
29
+ fn sz_find_char_from(
30
+ haystack: *const c_void,
31
+ haystack_length: usize,
32
+ needle: *const c_void,
33
+ needle_length: usize,
34
+ ) -> *const c_void;
35
+
36
+ fn sz_rfind_char_from(
37
+ haystack: *const c_void,
38
+ haystack_length: usize,
39
+ needle: *const c_void,
40
+ needle_length: usize,
41
+ ) -> *const c_void;
42
+
43
+ fn sz_find_char_not_from(
44
+ haystack: *const c_void,
45
+ haystack_length: usize,
46
+ needle: *const c_void,
47
+ needle_length: usize,
48
+ ) -> *const c_void;
49
+
50
+ fn sz_rfind_char_not_from(
51
+ haystack: *const c_void,
52
+ haystack_length: usize,
53
+ needle: *const c_void,
54
+ needle_length: usize,
55
+ ) -> *const c_void;
56
+
57
+ fn sz_edit_distance(
58
+ haystack1: *const c_void,
59
+ haystack1_length: usize,
60
+ haystack2: *const c_void,
61
+ haystack2_length: usize,
62
+ bound: usize,
63
+ allocator: *const c_void,
64
+ ) -> usize;
65
+
66
+ fn sz_edit_distance_utf8(
67
+ haystack1: *const c_void,
68
+ haystack1_length: usize,
69
+ haystack2: *const c_void,
70
+ haystack2_length: usize,
71
+ bound: usize,
72
+ allocator: *const c_void,
73
+ ) -> usize;
74
+
75
+ fn sz_hamming_distance(
76
+ haystack1: *const c_void,
77
+ haystack1_length: usize,
78
+ haystack2: *const c_void,
79
+ haystack2_length: usize,
80
+ bound: usize,
81
+ ) -> usize;
82
+
83
+ fn sz_hamming_distance_utf8(
84
+ haystack1: *const c_void,
85
+ haystack1_length: usize,
86
+ haystack2: *const c_void,
87
+ haystack2_length: usize,
88
+ bound: usize,
89
+ ) -> usize;
90
+
91
+ fn sz_alignment_score(
92
+ haystack1: *const c_void,
93
+ haystack1_length: usize,
94
+ haystack2: *const c_void,
95
+ haystack2_length: usize,
96
+ matrix: *const c_void,
97
+ gap: i8,
98
+ allocator: *const c_void,
99
+ ) -> isize;
100
+
101
+ // type RandomGeneratorT = fn(*mut c_void) -> u64;
102
+
103
+ fn sz_generate(
104
+ alphabet: *const c_void,
105
+ alphabet_size: usize,
106
+ text: *mut c_void,
107
+ length: usize,
108
+ generate: *const c_void,
109
+ generator: *mut c_void,
110
+ );
111
+ }
101
112
 
102
- impl<T, N> StringZilla<N> for T
103
- where
104
- T: AsRef<[u8]>,
105
- N: AsRef<[u8]>,
106
- {
107
- fn sz_find(&self, needle: N) -> Option<usize> {
108
- let haystack_ref = self.as_ref();
113
+ /// Locates the first matching substring within `haystack` that equals `needle`.
114
+ /// This function is similar to the `memmem()` function in LibC, but, unlike `strstr()`,
115
+ /// it requires the length of both haystack and needle to be known beforehand.
116
+ ///
117
+ /// # Arguments
118
+ ///
119
+ /// * `haystack`: The byte slice to search.
120
+ /// * `needle`: The byte slice to find within the haystack.
121
+ ///
122
+ /// # Returns
123
+ ///
124
+ /// An `Option<usize>` representing the starting index of the first occurrence of `needle`
125
+ /// within `haystack` if found, otherwise `None`.
126
+ pub fn find<H, N>(haystack: H, needle: N) -> Option<usize>
127
+ where
128
+ H: AsRef<[u8]>,
129
+ N: AsRef<[u8]>,
130
+ {
131
+ let haystack_ref = haystack.as_ref();
109
132
  let needle_ref = needle.as_ref();
110
133
  let haystack_pointer = haystack_ref.as_ptr() as _;
111
134
  let haystack_length = haystack_ref.len();
@@ -127,8 +150,25 @@ where
127
150
  }
128
151
  }
129
152
 
130
- fn sz_rfind(&self, needle: N) -> Option<usize> {
131
- let haystack_ref = self.as_ref();
153
+ /// Locates the last matching substring within `haystack` that equals `needle`.
154
+ /// This function is useful for finding the most recent or last occurrence of a pattern
155
+ /// within a byte slice.
156
+ ///
157
+ /// # Arguments
158
+ ///
159
+ /// * `haystack`: The byte slice to search.
160
+ /// * `needle`: The byte slice to find within the haystack.
161
+ ///
162
+ /// # Returns
163
+ ///
164
+ /// An `Option<usize>` representing the starting index of the last occurrence of `needle`
165
+ /// within `haystack` if found, otherwise `None`.
166
+ pub fn rfind<H, N>(haystack: H, needle: N) -> Option<usize>
167
+ where
168
+ H: AsRef<[u8]>,
169
+ N: AsRef<[u8]>,
170
+ {
171
+ let haystack_ref = haystack.as_ref();
132
172
  let needle_ref = needle.as_ref();
133
173
  let haystack_pointer = haystack_ref.as_ptr() as _;
134
174
  let haystack_length = haystack_ref.len();
@@ -150,8 +190,25 @@ where
150
190
  }
151
191
  }
152
192
 
153
- fn sz_find_char_from(&self, needles: N) -> Option<usize> {
154
- let haystack_ref = self.as_ref();
193
+ /// Finds the index of the first character in `haystack` that is also present in `needles`.
194
+ /// This function is particularly useful for parsing and tokenization tasks where a set of
195
+ /// delimiter characters is used.
196
+ ///
197
+ /// # Arguments
198
+ ///
199
+ /// * `haystack`: The byte slice to search.
200
+ /// * `needles`: The set of bytes to search for within the haystack.
201
+ ///
202
+ /// # Returns
203
+ ///
204
+ /// An `Option<usize>` representing the index of the first occurrence of any byte from
205
+ /// `needles` within `haystack`, if found, otherwise `None`.
206
+ pub fn find_char_from<H, N>(haystack: H, needles: N) -> Option<usize>
207
+ where
208
+ H: AsRef<[u8]>,
209
+ N: AsRef<[u8]>,
210
+ {
211
+ let haystack_ref = haystack.as_ref();
155
212
  let needles_ref = needles.as_ref();
156
213
  let haystack_pointer = haystack_ref.as_ptr() as _;
157
214
  let haystack_length = haystack_ref.len();
@@ -172,8 +229,25 @@ where
172
229
  }
173
230
  }
174
231
 
175
- fn sz_rfind_char_from(&self, needles: N) -> Option<usize> {
176
- let haystack_ref = self.as_ref();
232
+ /// Finds the index of the last character in `haystack` that is also present in `needles`.
233
+ /// This can be used to find the last occurrence of any character from a specified set,
234
+ /// useful in parsing scenarios such as finding the last delimiter in a string.
235
+ ///
236
+ /// # Arguments
237
+ ///
238
+ /// * `haystack`: The byte slice to search.
239
+ /// * `needles`: The set of bytes to search for within the haystack.
240
+ ///
241
+ /// # Returns
242
+ ///
243
+ /// An `Option<usize>` representing the index of the last occurrence of any byte from
244
+ /// `needles` within `haystack`, if found, otherwise `None`.
245
+ pub fn rfind_char_from<H, N>(haystack: H, needles: N) -> Option<usize>
246
+ where
247
+ H: AsRef<[u8]>,
248
+ N: AsRef<[u8]>,
249
+ {
250
+ let haystack_ref = haystack.as_ref();
177
251
  let needles_ref = needles.as_ref();
178
252
  let haystack_pointer = haystack_ref.as_ptr() as _;
179
253
  let haystack_length = haystack_ref.len();
@@ -194,8 +268,25 @@ where
194
268
  }
195
269
  }
196
270
 
197
- fn sz_find_char_not_from(&self, needles: N) -> Option<usize> {
198
- let haystack_ref = self.as_ref();
271
+ /// Finds the index of the first character in `haystack` that is not present in `needles`.
272
+ /// This function is useful for skipping over a known set of characters and finding the
273
+ /// first character that does not belong to that set.
274
+ ///
275
+ /// # Arguments
276
+ ///
277
+ /// * `haystack`: The byte slice to search.
278
+ /// * `needles`: The set of bytes that should not be matched within the haystack.
279
+ ///
280
+ /// # Returns
281
+ ///
282
+ /// An `Option<usize>` representing the index of the first occurrence of any byte not in
283
+ /// `needles` within `haystack`, if found, otherwise `None`.
284
+ pub fn find_char_not_from<H, N>(haystack: H, needles: N) -> Option<usize>
285
+ where
286
+ H: AsRef<[u8]>,
287
+ N: AsRef<[u8]>,
288
+ {
289
+ let haystack_ref = haystack.as_ref();
199
290
  let needles_ref = needles.as_ref();
200
291
  let haystack_pointer = haystack_ref.as_ptr() as _;
201
292
  let haystack_length = haystack_ref.len();
@@ -216,8 +307,25 @@ where
216
307
  }
217
308
  }
218
309
 
219
- fn sz_rfind_char_not_from(&self, needles: N) -> Option<usize> {
220
- let haystack_ref = self.as_ref();
310
+ /// Finds the index of the last character in `haystack` that is not present in `needles`.
311
+ /// Useful for text processing tasks such as trimming trailing characters that belong to
312
+ /// a specified set.
313
+ ///
314
+ /// # Arguments
315
+ ///
316
+ /// * `haystack`: The byte slice to search.
317
+ /// * `needles`: The set of bytes that should not be matched within the haystack.
318
+ ///
319
+ /// # Returns
320
+ ///
321
+ /// An `Option<usize>` representing the index of the last occurrence of any byte not in
322
+ /// `needles` within `haystack`, if found, otherwise `None`.
323
+ pub fn rfind_char_not_from<H, N>(haystack: H, needles: N) -> Option<usize>
324
+ where
325
+ H: AsRef<[u8]>,
326
+ N: AsRef<[u8]>,
327
+ {
328
+ let haystack_ref = haystack.as_ref();
221
329
  let needles_ref = needles.as_ref();
222
330
  let haystack_pointer = haystack_ref.as_ptr() as _;
223
331
  let haystack_length = haystack_ref.len();
@@ -238,57 +346,299 @@ where
238
346
  }
239
347
  }
240
348
 
241
- fn sz_edit_distance(&self, needle: N) -> usize {
242
- let haystack_ref = self.as_ref();
243
- let needle_ref = needle.as_ref();
244
- let haystack_length = haystack_ref.len();
245
- let needle_length = needle_ref.len();
246
- let haystack_pointer = haystack_ref.as_ptr() as _;
247
- let needle_pointer = needle_ref.as_ptr() as _;
349
+ /// Computes the Levenshtein edit distance between two strings, using the Wagner-Fisher
350
+ /// algorithm. This measure is widely used in applications like spell-checking, DNA sequence
351
+ /// analysis.
352
+ ///
353
+ /// # Arguments
354
+ ///
355
+ /// * `first`: The first byte slice.
356
+ /// * `second`: The second byte slice.
357
+ /// * `bound`: The maximum distance to compute, allowing for early exit.
358
+ ///
359
+ /// # Returns
360
+ ///
361
+ /// A `usize` representing the minimum number of single-character edits (insertions,
362
+ /// deletions, or substitutions) required to change `first` into `second`.
363
+ pub fn edit_distance_bounded<F, S>(first: F, second: S, bound: usize) -> usize
364
+ where
365
+ F: AsRef<[u8]>,
366
+ S: AsRef<[u8]>,
367
+ {
368
+ let first_ref = first.as_ref();
369
+ let second_ref = second.as_ref();
370
+ let first_length = first_ref.len();
371
+ let second_length = second_ref.len();
372
+ let first_pointer = first_ref.as_ptr() as _;
373
+ let second_pointer = second_ref.as_ptr() as _;
248
374
  unsafe {
249
375
  sz_edit_distance(
250
- haystack_pointer,
251
- haystack_length,
252
- needle_pointer,
253
- needle_length,
376
+ first_pointer,
377
+ first_length,
378
+ second_pointer,
379
+ second_length,
254
380
  // Upper bound on the distance, that allows us to exit early. If zero is
255
381
  // passed, the maximum possible distance will be equal to the length of
256
382
  // the longer input.
257
- 0,
383
+ bound,
258
384
  // Uses the default allocator
259
385
  core::ptr::null(),
260
386
  )
261
387
  }
262
388
  }
263
389
 
264
- fn sz_alignment_score(&self, needle: N, matrix: [[i8; 256]; 256], gap: i8) -> isize {
265
- let haystack_ref = self.as_ref();
266
- let needle_ref = needle.as_ref();
267
- let haystack_length = haystack_ref.len();
268
- let needle_length = needle_ref.len();
269
- let haystack_pointer = haystack_ref.as_ptr() as _;
270
- let needle_pointer = needle_ref.as_ptr() as _;
390
+ /// Computes the Levenshtein edit distance between two UTF8 strings, using the Wagner-Fisher
391
+ /// algorithm. This measure is widely used in applications like spell-checking.
392
+ ///
393
+ /// # Arguments
394
+ ///
395
+ /// * `first`: The first byte slice.
396
+ /// * `second`: The second byte slice.
397
+ /// * `bound`: The maximum distance to compute, allowing for early exit.
398
+ ///
399
+ /// # Returns
400
+ ///
401
+ /// A `usize` representing the minimum number of single-character edits (insertions,
402
+ /// deletions, or substitutions) required to change `first` into `second`.
403
+ pub fn edit_distance_utf8_bounded<F, S>(first: F, second: S, bound: usize) -> usize
404
+ where
405
+ F: AsRef<[u8]>,
406
+ S: AsRef<[u8]>,
407
+ {
408
+ let first_ref = first.as_ref();
409
+ let second_ref = second.as_ref();
410
+ let first_length = first_ref.len();
411
+ let second_length = second_ref.len();
412
+ let first_pointer = first_ref.as_ptr() as _;
413
+ let second_pointer = second_ref.as_ptr() as _;
414
+ unsafe {
415
+ sz_edit_distance_utf8(
416
+ first_pointer,
417
+ first_length,
418
+ second_pointer,
419
+ second_length,
420
+ // Upper bound on the distance, that allows us to exit early. If zero is
421
+ // passed, the maximum possible distance will be equal to the length of
422
+ // the longer input.
423
+ bound,
424
+ // Uses the default allocator
425
+ core::ptr::null(),
426
+ )
427
+ }
428
+ }
429
+
430
+ /// Computes the Levenshtein edit distance between two strings, using the Wagner-Fisher
431
+ /// algorithm. This measure is widely used in applications like spell-checking, DNA sequence
432
+ /// analysis.
433
+ ///
434
+ /// # Arguments
435
+ ///
436
+ /// * `first`: The first byte slice.
437
+ /// * `second`: The second byte slice.
438
+ ///
439
+ /// # Returns
440
+ ///
441
+ /// A `usize` representing the minimum number of single-character edits (insertions,
442
+ /// deletions, or substitutions) required to change `first` into `second`.
443
+ pub fn edit_distance<F, S>(first: F, second: S) -> usize
444
+ where
445
+ F: AsRef<[u8]>,
446
+ S: AsRef<[u8]>,
447
+ {
448
+ edit_distance_bounded(first, second, 0)
449
+ }
450
+
451
+ /// Computes the Levenshtein edit distance between two UTF8 strings, using the Wagner-Fisher
452
+ /// algorithm. This measure is widely used in applications like spell-checking.
453
+ ///
454
+ /// # Arguments
455
+ ///
456
+ /// * `first`: The first byte slice.
457
+ /// * `second`: The second byte slice.
458
+ ///
459
+ /// # Returns
460
+ ///
461
+ /// A `usize` representing the minimum number of single-character edits (insertions,
462
+ /// deletions, or substitutions) required to change `first` into `second`.
463
+ pub fn edit_distance_utf8<F, S>(first: F, second: S) -> usize
464
+ where
465
+ F: AsRef<[u8]>,
466
+ S: AsRef<[u8]>,
467
+ {
468
+ edit_distance_utf8_bounded(first, second, 0)
469
+ }
470
+
471
+ /// Computes the Hamming edit distance between two strings, counting the number of substituted characters.
472
+ /// Difference in length is added to the result as well.
473
+ ///
474
+ /// # Arguments
475
+ ///
476
+ /// * `first`: The first byte slice.
477
+ /// * `second`: The second byte slice.
478
+ /// * `bound`: The maximum distance to compute, allowing for early exit.
479
+ ///
480
+ /// # Returns
481
+ ///
482
+ /// A `usize` representing the minimum number of single-character edits (substitutions) required to
483
+ /// change `first` into `second`.
484
+ pub fn hamming_distance_bounded<F, S>(first: F, second: S, bound: usize) -> usize
485
+ where
486
+ F: AsRef<[u8]>,
487
+ S: AsRef<[u8]>,
488
+ {
489
+ let first_ref = first.as_ref();
490
+ let second_ref = second.as_ref();
491
+ let first_length = first_ref.len();
492
+ let second_length = second_ref.len();
493
+ let first_pointer = first_ref.as_ptr() as _;
494
+ let second_pointer = second_ref.as_ptr() as _;
495
+ unsafe {
496
+ sz_hamming_distance(
497
+ first_pointer,
498
+ first_length,
499
+ second_pointer,
500
+ second_length,
501
+ // Upper bound on the distance, that allows us to exit early. If zero is
502
+ // passed, the maximum possible distance will be equal to the length of
503
+ // the longer input.
504
+ bound,
505
+ )
506
+ }
507
+ }
508
+
509
+ /// Computes the Hamming edit distance between two UTF8 strings, counting the number of substituted
510
+ /// variable-length characters. Difference in length is added to the result as well.
511
+ ///
512
+ /// # Arguments
513
+ ///
514
+ /// * `first`: The first byte slice.
515
+ /// * `second`: The second byte slice.
516
+ /// * `bound`: The maximum distance to compute, allowing for early exit.
517
+ ///
518
+ /// # Returns
519
+ ///
520
+ /// A `usize` representing the minimum number of single-character edits (substitutions) required to
521
+ /// change `first` into `second`.
522
+ pub fn hamming_distance_utf8_bounded<F, S>(first: F, second: S, bound: usize) -> usize
523
+ where
524
+ F: AsRef<[u8]>,
525
+ S: AsRef<[u8]>,
526
+ {
527
+ let first_ref = first.as_ref();
528
+ let second_ref = second.as_ref();
529
+ let first_length = first_ref.len();
530
+ let second_length = second_ref.len();
531
+ let first_pointer = first_ref.as_ptr() as _;
532
+ let second_pointer = second_ref.as_ptr() as _;
533
+ unsafe {
534
+ sz_hamming_distance_utf8(
535
+ first_pointer,
536
+ first_length,
537
+ second_pointer,
538
+ second_length,
539
+ // Upper bound on the distance, that allows us to exit early. If zero is
540
+ // passed, the maximum possible distance will be equal to the length of
541
+ // the longer input.
542
+ bound,
543
+ )
544
+ }
545
+ }
546
+
547
+ /// Computes the Hamming edit distance between two strings, counting the number of substituted characters.
548
+ /// Difference in length is added to the result as well.
549
+ ///
550
+ /// # Arguments
551
+ ///
552
+ /// * `first`: The first byte slice.
553
+ /// * `second`: The second byte slice.
554
+ ///
555
+ /// # Returns
556
+ ///
557
+ /// A `usize` representing the minimum number of single-character edits (substitutions) required to
558
+ /// change `first` into `second`.
559
+ pub fn hamming_distance<F, S>(first: F, second: S) -> usize
560
+ where
561
+ F: AsRef<[u8]>,
562
+ S: AsRef<[u8]>,
563
+ {
564
+ hamming_distance_bounded(first, second, 0)
565
+ }
566
+
567
+ /// Computes the Hamming edit distance between two UTF8 strings, counting the number of substituted
568
+ /// variable-length characters. Difference in length is added to the result as well.
569
+ ///
570
+ /// # Arguments
571
+ ///
572
+ /// * `first`: The first byte slice.
573
+ /// * `second`: The second byte slice.
574
+ ///
575
+ /// # Returns
576
+ ///
577
+ /// A `usize` representing the minimum number of single-character edits (substitutions) required to
578
+ /// change `first` into `second`.
579
+ pub fn hamming_distance_utf8<F, S>(first: F, second: S) -> usize
580
+ where
581
+ F: AsRef<[u8]>,
582
+ S: AsRef<[u8]>,
583
+ {
584
+ hamming_distance_utf8_bounded(first, second, 0)
585
+ }
586
+
587
+ /// Computes the Needleman-Wunsch alignment score for two strings. This function is
588
+ /// particularly used in bioinformatics for sequence alignment but is also applicable in
589
+ /// other domains requiring detailed comparison between two strings, including gap and
590
+ /// substitution penalties.
591
+ ///
592
+ /// # Arguments
593
+ ///
594
+ /// * `first`: The first byte slice to align.
595
+ /// * `second`: The second byte slice to align.
596
+ /// * `matrix`: The substitution matrix used for scoring.
597
+ /// * `gap`: The penalty for each gap introduced during alignment.
598
+ ///
599
+ /// # Returns
600
+ ///
601
+ /// An `isize` representing the total alignment score, where higher scores indicate better
602
+ /// alignment between the two strings, considering the specified gap penalties and
603
+ /// substitution matrix.
604
+ pub fn alignment_score<F, S>(first: F, second: S, matrix: [[i8; 256]; 256], gap: i8) -> isize
605
+ where
606
+ F: AsRef<[u8]>,
607
+ S: AsRef<[u8]>,
608
+ {
609
+ let first_ref = first.as_ref();
610
+ let second_ref = second.as_ref();
611
+ let first_length = first_ref.len();
612
+ let second_length = second_ref.len();
613
+ let first_pointer = first_ref.as_ptr() as _;
614
+ let second_pointer = second_ref.as_ptr() as _;
271
615
  unsafe {
272
616
  sz_alignment_score(
273
- haystack_pointer,
274
- haystack_length,
275
- needle_pointer,
276
- needle_length,
617
+ first_pointer,
618
+ first_length,
619
+ second_pointer,
620
+ second_length,
277
621
  matrix.as_ptr() as _,
278
622
  gap,
279
623
  core::ptr::null(),
280
624
  )
281
625
  }
282
626
  }
283
- }
284
627
 
285
- #[cfg(test)]
286
- mod tests {
287
- use std::borrow::Cow;
288
-
289
- use crate::StringZilla;
290
-
291
- fn unary_substitution_costs() -> [[i8; 256]; 256] {
628
+ /// Generates a default substitution matrix for use with the Needleman-Wunsch
629
+ /// alignment algorithm. This matrix is initialized such that diagonal entries
630
+ /// (representing matching characters) are zero, and off-diagonal entries
631
+ /// (representing mismatches) are -1. This setup effectively produces distances
632
+ /// equal to the negative Levenshtein edit distance, suitable for basic sequence
633
+ /// alignment tasks where all mismatches are penalized equally and there are no
634
+ /// rewards for matches.
635
+ ///
636
+ /// # Returns
637
+ ///
638
+ /// A 256x256 array of `i8`, where each element represents the substitution cost
639
+ /// between two characters (byte values). Matching characters are assigned a cost
640
+ /// of 0, and non-matching characters are assigned a cost of -1.
641
+ pub fn unary_substitution_costs() -> [[i8; 256]; 256] {
292
642
  let mut result = [[0; 256]; 256];
293
643
 
294
644
  for i in 0..256 {
@@ -300,38 +650,187 @@ mod tests {
300
650
  result
301
651
  }
302
652
 
653
+ /// Randomizes the contents of a given byte slice `text` using characters from
654
+ /// a specified `alphabet`. This function mutates `text` in place, replacing each
655
+ /// byte with a random one from `alphabet`. It is designed for situations where
656
+ /// you need to generate random strings or data sequences based on a specific set
657
+ /// of characters, such as generating random DNA sequences or testing inputs.
658
+ ///
659
+ /// # Type Parameters
660
+ ///
661
+ /// * `T`: The type of the text to be randomized. Must be mutable and convertible to a byte slice.
662
+ /// * `A`: The type of the alphabet. Must be convertible to a byte slice.
663
+ ///
664
+ /// # Arguments
665
+ ///
666
+ /// * `text`: A mutable reference to the data to randomize. This data will be mutated in place.
667
+ /// * `alphabet`: A reference to the byte slice representing the alphabet to use for randomization.
668
+ ///
669
+ /// # Examples
670
+ ///
671
+ /// ```
672
+ /// use stringzilla::sz;
673
+ /// let mut my_text = vec![0; 10]; // A buffer to randomize
674
+ /// let alphabet = b"ACTG"; // Using a DNA alphabet
675
+ /// sz::randomize(&mut my_text, &alphabet);
676
+ /// ```
677
+ ///
678
+ /// After than, `my_text` is filled with random 'A', 'C', 'T', or 'G' values.
679
+ pub fn randomize<T, A>(text: &mut T, alphabet: &A)
680
+ where
681
+ T: AsMut<[u8]> + ?Sized, // Allows for mutable references to dynamically sized types.
682
+ A: AsRef<[u8]> + ?Sized, // Allows for references to dynamically sized types.
683
+ {
684
+ let text_slice = text.as_mut();
685
+ let alphabet_slice = alphabet.as_ref();
686
+ unsafe {
687
+ sz_generate(
688
+ alphabet_slice.as_ptr() as *const c_void,
689
+ alphabet_slice.len(),
690
+ text_slice.as_mut_ptr() as *mut c_void,
691
+ text_slice.len(),
692
+ core::ptr::null(),
693
+ core::ptr::null_mut(),
694
+ );
695
+ }
696
+ }
697
+ }
698
+
699
+ /// The [StringZilla] trait provides a collection of string searching and manipulation functionalities.
700
+ pub trait StringZilla<N>
701
+ where
702
+ N: AsRef<[u8]>,
703
+ {
704
+ fn sz_find(&self, needle: N) -> Option<usize>;
705
+ fn sz_rfind(&self, needle: N) -> Option<usize>;
706
+ fn sz_find_char_from(&self, needles: N) -> Option<usize>;
707
+ fn sz_rfind_char_from(&self, needles: N) -> Option<usize>;
708
+ fn sz_find_char_not_from(&self, needles: N) -> Option<usize>;
709
+ fn sz_rfind_char_not_from(&self, needles: N) -> Option<usize>;
710
+ fn sz_edit_distance(&self, other: N) -> usize;
711
+ fn sz_alignment_score(&self, other: N, matrix: [[i8; 256]; 256], gap: i8) -> isize;
712
+ }
713
+
714
+ impl<T, N> StringZilla<N> for T
715
+ where
716
+ T: AsRef<[u8]>,
717
+ N: AsRef<[u8]>,
718
+ {
719
+ fn sz_find(&self, needle: N) -> Option<usize> {
720
+ sz::find(self, needle)
721
+ }
722
+
723
+ fn sz_rfind(&self, needle: N) -> Option<usize> {
724
+ sz::rfind(self, needle)
725
+ }
726
+
727
+ fn sz_find_char_from(&self, needles: N) -> Option<usize> {
728
+ sz::find_char_from(self, needles)
729
+ }
730
+
731
+ fn sz_rfind_char_from(&self, needles: N) -> Option<usize> {
732
+ sz::rfind_char_from(self, needles)
733
+ }
734
+
735
+ fn sz_find_char_not_from(&self, needles: N) -> Option<usize> {
736
+ sz::find_char_not_from(self, needles)
737
+ }
738
+
739
+ fn sz_rfind_char_not_from(&self, needles: N) -> Option<usize> {
740
+ sz::rfind_char_not_from(self, needles)
741
+ }
742
+
743
+ fn sz_edit_distance(&self, other: N) -> usize {
744
+ sz::edit_distance(self, other)
745
+ }
746
+
747
+ fn sz_alignment_score(&self, other: N, matrix: [[i8; 256]; 256], gap: i8) -> isize {
748
+ sz::alignment_score(self, other, matrix, gap)
749
+ }
750
+ }
751
+
752
+ /// The [MutableStringZilla] trait provides tools for updating strings.
753
+ trait MutableStringZilla<A>
754
+ where
755
+ A: AsRef<[u8]>,
756
+ {
757
+ /// Generates a random string for a given alphabet.
758
+ /// Replaces the buffer with a random string of the same length.
759
+ fn sz_randomize(&mut self, alphabet: A);
760
+ }
761
+
762
+ impl<T, A> MutableStringZilla<A> for T
763
+ where
764
+ T: AsMut<[u8]>,
765
+ A: AsRef<[u8]>,
766
+ {
767
+ fn sz_randomize(&mut self, alphabet: A) {
768
+ let self_mut = self.as_mut();
769
+ let alphabet_ref = alphabet.as_ref();
770
+ sz::randomize(self_mut, alphabet_ref);
771
+ }
772
+ }
773
+
774
+ #[cfg(test)]
775
+ mod tests {
776
+ use std::borrow::Cow;
777
+
778
+ use crate::sz;
779
+ use crate::MutableStringZilla;
780
+ use crate::StringZilla;
781
+
782
+ #[test]
783
+ fn hamming() {
784
+ assert_eq!(sz::hamming_distance("hello", "hello"), 0);
785
+ assert_eq!(sz::hamming_distance("hello", "hell"), 1);
786
+ assert_eq!(sz::hamming_distance("abc", "adc"), 1);
787
+
788
+ assert_eq!(sz::hamming_distance_bounded("abcdefgh", "ABCDEFGH", 2), 2);
789
+ assert_eq!(sz::hamming_distance_utf8("αβγδ", "αγγδ"), 1);
790
+ }
791
+
303
792
  #[test]
304
793
  fn levenshtein() {
305
- assert_eq!("hello".sz_edit_distance("hell"), 1);
306
- assert_eq!("hello".sz_edit_distance("hell"), 1);
307
- assert_eq!("abc".sz_edit_distance(""), 3);
308
- assert_eq!("abc".sz_edit_distance("ac"), 1);
309
- assert_eq!("abc".sz_edit_distance("a_bc"), 1);
310
- assert_eq!("abc".sz_edit_distance("adc"), 1);
311
- assert_eq!("ggbuzgjux{}l".sz_edit_distance("gbuzgjux{}l"), 1);
312
- assert_eq!("abcdefgABCDEFG".sz_edit_distance("ABCDEFGabcdefg"), 14);
313
- assert_eq!("fitting".sz_edit_distance("kitty"), 4);
314
- assert_eq!("smitten".sz_edit_distance("mitten"), 1);
794
+ assert_eq!(sz::edit_distance("hello", "hell"), 1);
795
+ assert_eq!(sz::edit_distance("hello", "hell"), 1);
796
+ assert_eq!(sz::edit_distance("abc", ""), 3);
797
+ assert_eq!(sz::edit_distance("abc", "ac"), 1);
798
+ assert_eq!(sz::edit_distance("abc", "a_bc"), 1);
799
+ assert_eq!(sz::edit_distance("abc", "adc"), 1);
800
+ assert_eq!(sz::edit_distance("fitting", "kitty"), 4);
801
+ assert_eq!(sz::edit_distance("smitten", "mitten"), 1);
802
+ assert_eq!(sz::edit_distance("ggbuzgjux{}l", "gbuzgjux{}l"), 1);
803
+ assert_eq!(sz::edit_distance("abcdefgABCDEFG", "ABCDEFGabcdefg"), 14);
804
+
805
+ assert_eq!(sz::edit_distance_bounded("fitting", "kitty", 2), 2);
806
+ assert_eq!(sz::edit_distance_utf8("façade", "facade"), 1);
315
807
  }
316
808
 
317
809
  #[test]
318
810
  fn needleman() {
319
- let costs_vector = unary_substitution_costs();
320
- assert_eq!("listen".sz_alignment_score("silent", costs_vector, -1), -4);
811
+ let costs_vector = sz::unary_substitution_costs();
812
+ assert_eq!(
813
+ sz::alignment_score("listen", "silent", costs_vector, -1),
814
+ -4
815
+ );
321
816
  assert_eq!(
322
- "abcdefgABCDEFG".sz_alignment_score("ABCDEFGabcdefg", costs_vector, -1),
817
+ sz::alignment_score("abcdefgABCDEFG", "ABCDEFGabcdefg", costs_vector, -1),
323
818
  -14
324
819
  );
325
- assert_eq!("hello".sz_alignment_score("hello", costs_vector, -1), 0);
326
- assert_eq!("hello".sz_alignment_score("hell", costs_vector, -1), -1);
820
+ assert_eq!(sz::alignment_score("hello", "hello", costs_vector, -1), 0);
821
+ assert_eq!(sz::alignment_score("hello", "hell", costs_vector, -1), -1);
327
822
  }
328
823
 
329
824
  #[test]
330
- fn basics() {
825
+ fn search() {
331
826
  let my_string: String = String::from("Hello, world!");
332
827
  let my_str: &str = my_string.as_str();
333
828
  let my_cow_str: Cow<'_, str> = Cow::from(&my_string);
334
829
 
830
+ // Identical to `memchr::memmem::find` and `memchr::memmem::rfind` functions
831
+ assert_eq!(sz::find("Hello, world!", "world"), Some(7));
832
+ assert_eq!(sz::rfind("Hello, world!", "world"), Some(7));
833
+
335
834
  // Use the generic function with a String
336
835
  assert_eq!(my_string.sz_find("world"), Some(7));
337
836
  assert_eq!(my_string.sz_rfind("world"), Some(7));
@@ -359,4 +858,16 @@ mod tests {
359
858
  Some(12)
360
859
  );
361
860
  }
861
+
862
+ #[test]
863
+ fn randomize() {
864
+ let mut text: Vec<u8> = vec![0; 10]; // A buffer of ten zeros
865
+ let alphabet: &[u8] = b"abcd"; // A byte slice alphabet
866
+ text.sz_randomize(alphabet);
867
+
868
+ // Iterate throught text and check that it only contains letters from the alphabet
869
+ assert!(text
870
+ .iter()
871
+ .all(|&b| b == b'd' || b == b'c' || b == b'b' || b == b'a'));
872
+ }
362
873
  }