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.
- package/CITATION.cff +1 -1
- package/CONTRIBUTING.md +10 -2
- package/Cargo.toml +1 -1
- package/README.md +12 -15
- package/VERSION +1 -1
- package/c/test.c +58 -50
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +20 -10
- package/javascript/types/usearch.d.ts.map +1 -1
- package/javascript/usearch.js +4 -1
- package/package.json +1 -1
- package/simsimd/CONTRIBUTING.md +88 -0
- package/simsimd/Cargo.lock +4 -3
- package/simsimd/Cargo.toml +10 -2
- package/simsimd/README.md +102 -60
- package/simsimd/include/simsimd/binary.h +44 -0
- package/simsimd/include/simsimd/complex.h +174 -0
- package/simsimd/include/simsimd/simsimd.h +18 -9
- package/simsimd/include/simsimd/spatial.h +128 -36
- package/simsimd/include/simsimd/types.h +3 -2
- package/simsimd/package-lock.json +5 -5
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/native.rs +12 -9
- package/simsimd/rust/lib.c +32 -55
- package/simsimd/rust/lib.rs +374 -56
- package/stringzilla/CONTRIBUTING.md +17 -0
- package/stringzilla/Cargo.lock +1 -1
- package/stringzilla/Cargo.toml +1 -1
- package/stringzilla/README.md +175 -34
- package/stringzilla/VERSION +1 -1
- package/stringzilla/build.rs +77 -4
- package/stringzilla/c/lib.c +33 -0
- package/stringzilla/cli/README.md +30 -13
- package/stringzilla/cli/__init__.py +0 -0
- package/stringzilla/cli/split.py +66 -33
- package/stringzilla/cli/wc.py +101 -21
- package/stringzilla/include/stringzilla/experimental.h +122 -1
- package/stringzilla/include/stringzilla/stringzilla.h +262 -132
- package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
- package/stringzilla/package.json +1 -1
- package/stringzilla/rust/lib.rs +673 -162
- package/wasmer.toml +1 -1
package/stringzilla/rust/lib.rs
CHANGED
|
@@ -1,111 +1,134 @@
|
|
|
1
1
|
#![cfg_attr(not(test), no_std)]
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
383
|
+
bound,
|
|
258
384
|
// Uses the default allocator
|
|
259
385
|
core::ptr::null(),
|
|
260
386
|
)
|
|
261
387
|
}
|
|
262
388
|
}
|
|
263
389
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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"
|
|
306
|
-
assert_eq!("hello"
|
|
307
|
-
assert_eq!("abc"
|
|
308
|
-
assert_eq!("abc"
|
|
309
|
-
assert_eq!("abc"
|
|
310
|
-
assert_eq!("abc"
|
|
311
|
-
assert_eq!("
|
|
312
|
-
assert_eq!("
|
|
313
|
-
assert_eq!("
|
|
314
|
-
assert_eq!("
|
|
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!(
|
|
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"
|
|
817
|
+
sz::alignment_score("abcdefgABCDEFG", "ABCDEFGabcdefg", costs_vector, -1),
|
|
323
818
|
-14
|
|
324
819
|
);
|
|
325
|
-
assert_eq!("hello"
|
|
326
|
-
assert_eq!("hello"
|
|
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
|
|
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
|
}
|