Move the dictionary builder's suffix-array construction from
lib/dictBuilder/divsufsort.c to rust/src/divsufsort.rs, the first
dictBuilder module to migrate. It rides on the dict-builder cargo
feature dimension introduced by the previous commit.
divsufsort() is a self-contained algorithm (two-stage sort of type-B*
substrings via sssort, rank refinement via trsort, then induced sorting
of the full array), so its context-free signature allows a direct symbol
takeover: the Rust #[no_mangle] export provides the existing `divsufsort`
symbol and the C file becomes a declaration-only shim that just keeps the
header's prototypes in the build. Only divsufsort() moved; divbwt() has
no callers anywhere in zstd, so it is now declaration-only, keeping the
Rust export surface minimal. The unused openMP parameter is retained for
signature compatibility (zstd never defines LIBBSC_OPENMP).
The port is a mechanical translation of the exact configuration zstd
compiles: ALPHABET_SIZE=256, SS_INSERTIONSORT_THRESHOLD=8,
SS_BLOCKSIZE=1024, SS_MISORT_STACKSIZE=16, SS_SMERGE_STACKSIZE=32,
TR_STACKSIZE=64. Every C `int*` cursor into the SA buffer becomes an
`isize` index into a single `&mut [i32]` slice, preserving the pointer
arithmetic (including transient one-before-the-range cursors and the
bitwise-complement rank marking) while staying bounds-checked; all value
arithmetic keeps C int semantics. The C -1/-2 error results are
preserved, with Vec::try_reserve_exact standing in for the bucket-array
malloc failure path. Behavior is bit-identical by construction and by
measurement (see test plan); runtime on an 11 MB training buffer is
within ~5% of the C build end-to-end.
Users see no behavioral change: dictionaries trained through
ZDICT_trainFromBuffer_legacy() are byte-identical to the C build. The
only external difference is that the never-called `divbwt` symbol is no
longer defined in the library.
Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
-- -D warnings && cargo test --all-targets && cargo build --release
(125 tests pass; new unit tests cover empty/one/two-byte inputs,
all-equal bytes, an exact hand-computed "abracadabra" SA, and
fixed-seed LCG buffers at 256-, 4-, and 2-symbol alphabets verified
against a naive reference sort plus permutation/sorted invariants)
- Feature matrix: cargo build --release --no-default-features
--features compression,decompression (and decompression-only,
compression-only, compression,dict-builder); `divsufsort` is exported
only when dict-builder is enabled
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests (includes
ZDICT training tests): pass
- make -C tests test-rust-lib-smoke: pass
- make -C tests test-invalidDictionaries: pass
- make -C programs zstd zstd-dictBuilder zstd-small zstd-compress
zstd-decompress: build; compress/decompress round-trip verified
- Byte-identity vs pristine C build (commit 959e4852): a harness calling
ZDICT_trainFromBuffer_legacy() (the only zstd path reaching
divsufsort) and divsufsort() directly, linked against both libzstd.a
builds, produces byte-identical dictionaries (80,288 B and full
112,640 B capacity) and byte-identical suffix arrays on a 1 MB source
set and an 11 MB binary/repetitive set; a differential driver over 148
random and structured buffers (sizes 3..6000, alphabets 1..256,
Fibonacci word, sawtooth, 6 KB near-constant) shows zero mismatches.
The CLI --train path could not be exercised because the Rust CLI
frontend rejects --train in both the pristine and ported builds (a
pre-existing migration gap unrelated to this change).
2757 lines
81 KiB
Rust
2757 lines
81 KiB
Rust
#![allow(clippy::missing_safety_doc)]
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
//! Suffix-array construction for the dictionary builder.
|
|
//!
|
|
//! Port of `lib/dictBuilder/divsufsort.c` (libdivsufsort-lite, Copyright (c)
|
|
//! 2003-2008 Yuta Mori, MIT license) in the exact configuration zstd compiles
|
|
//! it with: `ALPHABET_SIZE = 256`, `SS_INSERTIONSORT_THRESHOLD = 8`,
|
|
//! `SS_BLOCKSIZE = 1024`, and no OpenMP. Only `divsufsort()` is exported;
|
|
//! `divbwt()` has no callers anywhere in zstd and was not ported.
|
|
//!
|
|
//! The C implementation walks raw `int*` cursors through the caller's SA
|
|
//! buffer, including transient one-before-the-range positions. Every such
|
|
//! cursor is translated to an `isize` index into one `&mut [i32]` slice
|
|
//! covering the whole buffer, so all arithmetic — including the
|
|
//! bitwise-complement rank marking and the C `int` value semantics — matches
|
|
//! the original exactly while staying bounds-checked.
|
|
|
|
use std::os::raw::c_int;
|
|
use std::slice;
|
|
|
|
const BUCKET_A_SIZE: usize = 256; /* ALPHABET_SIZE */
|
|
const BUCKET_B_SIZE: usize = 256 * 256; /* ALPHABET_SIZE * ALPHABET_SIZE */
|
|
const ALPHABET_SIZE: i32 = 256;
|
|
const SS_INSERTIONSORT_THRESHOLD: isize = 8;
|
|
const SS_BLOCKSIZE: isize = 1024;
|
|
/* minstacksize = log(SS_BLOCKSIZE) / log(3) * 2 */
|
|
const SS_MISORT_STACKSIZE: usize = 16;
|
|
const SS_SMERGE_STACKSIZE: usize = 32;
|
|
const TR_INSERTIONSORT_THRESHOLD: isize = 8;
|
|
const TR_STACKSIZE: usize = 64;
|
|
|
|
#[rustfmt::skip]
|
|
static LG_TABLE: [i32; 256] = [
|
|
-1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
|
|
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
|
|
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
|
|
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
|
];
|
|
|
|
#[rustfmt::skip]
|
|
static SQQ_TABLE: [i32; 256] = [
|
|
0, 16, 22, 27, 32, 35, 39, 42, 45, 48, 50, 53, 55, 57, 59, 61,
|
|
64, 65, 67, 69, 71, 73, 75, 76, 78, 80, 81, 83, 84, 86, 87, 89,
|
|
90, 91, 93, 94, 96, 97, 98, 99, 101, 102, 103, 104, 106, 107, 108, 109,
|
|
110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
|
|
128, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
|
|
143, 144, 144, 145, 146, 147, 148, 149, 150, 150, 151, 152, 153, 154, 155, 155,
|
|
156, 157, 158, 159, 160, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168,
|
|
169, 170, 170, 171, 172, 173, 173, 174, 175, 176, 176, 177, 178, 178, 179, 180,
|
|
181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191,
|
|
192, 192, 193, 193, 194, 195, 195, 196, 197, 197, 198, 199, 199, 200, 201, 201,
|
|
202, 203, 203, 204, 204, 205, 206, 206, 207, 208, 208, 209, 209, 210, 211, 211,
|
|
212, 212, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 221,
|
|
221, 222, 222, 223, 224, 224, 225, 225, 226, 226, 227, 227, 228, 229, 229, 230,
|
|
230, 231, 231, 232, 232, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238,
|
|
239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247,
|
|
247, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255,
|
|
];
|
|
|
|
/* `ss_ilg` in its `256 <= SS_BLOCKSIZE` configuration. */
|
|
#[inline]
|
|
fn ss_ilg(n: isize) -> i32 {
|
|
let n = n as i32;
|
|
if n & 0xff00 != 0 {
|
|
8 + LG_TABLE[((n >> 8) & 0xff) as usize]
|
|
} else {
|
|
LG_TABLE[(n & 0xff) as usize]
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn ss_isqrt(x: isize) -> isize {
|
|
if x >= SS_BLOCKSIZE * SS_BLOCKSIZE {
|
|
return SS_BLOCKSIZE;
|
|
}
|
|
let x = x as i32;
|
|
let e = if (x as u32) & 0xffff_0000 != 0 {
|
|
if (x as u32) & 0xff00_0000 != 0 {
|
|
24 + LG_TABLE[((x >> 24) & 0xff) as usize]
|
|
} else {
|
|
16 + LG_TABLE[((x >> 16) & 0xff) as usize]
|
|
}
|
|
} else if x & 0xff00 != 0 {
|
|
8 + LG_TABLE[((x >> 8) & 0xff) as usize]
|
|
} else {
|
|
LG_TABLE[(x & 0xff) as usize]
|
|
};
|
|
|
|
let mut y;
|
|
if e >= 16 {
|
|
y = SQQ_TABLE[(x >> ((e - 6) - (e & 1))) as usize] << ((e >> 1) - 7);
|
|
if e >= 24 {
|
|
y = (y + 1 + x / y) >> 1;
|
|
}
|
|
y = (y + 1 + x / y) >> 1;
|
|
} else if e >= 8 {
|
|
y = (SQQ_TABLE[(x >> ((e - 6) - (e & 1))) as usize] >> (7 - (e >> 1))) + 1;
|
|
} else {
|
|
return (SQQ_TABLE[x as usize] >> 4) as isize;
|
|
}
|
|
|
|
(if x < y * y { y - 1 } else { y }) as isize
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/// Compares two suffixes. `(p10, p11)` and `(p20, p21)` are the `p[0]`/`p[1]`
|
|
/// pairs the C routine reads through its `const int*` arguments; passing the
|
|
/// values directly also serves `sssort()`'s local two-element `PAi` array.
|
|
#[inline]
|
|
fn ss_compare(t: &[u8], p10: i32, p11: i32, p20: i32, p21: i32, depth: i32) -> i32 {
|
|
let mut u1 = (depth + p10) as isize;
|
|
let mut u2 = (depth + p20) as isize;
|
|
let u1n = (p11 + 2) as isize;
|
|
let u2n = (p21 + 2) as isize;
|
|
|
|
while u1 < u1n && u2 < u2n && t[u1 as usize] == t[u2 as usize] {
|
|
u1 += 1;
|
|
u2 += 1;
|
|
}
|
|
|
|
if u1 < u1n {
|
|
if u2 < u2n {
|
|
t[u1 as usize] as i32 - t[u2 as usize] as i32
|
|
} else {
|
|
1
|
|
}
|
|
} else if u2 < u2n {
|
|
-1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
/// `ss_compare(T, p1, p2, depth)` for pointers `p1`/`p2` into the SA buffer.
|
|
#[inline]
|
|
fn ss_compare_pa(t: &[u8], sa: &[i32], p1: isize, p2: isize, depth: i32) -> i32 {
|
|
ss_compare(
|
|
t,
|
|
sa[p1 as usize],
|
|
sa[(p1 + 1) as usize],
|
|
sa[p2 as usize],
|
|
sa[(p2 + 1) as usize],
|
|
depth,
|
|
)
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Insertionsort for small size groups */
|
|
fn ss_insertionsort(t: &[u8], sa: &mut [i32], pa: isize, first: isize, last: isize, depth: i32) {
|
|
let mut i = last - 2;
|
|
while first <= i {
|
|
let t0 = sa[i as usize];
|
|
let mut j = i + 1;
|
|
let mut r;
|
|
loop {
|
|
r = ss_compare_pa(t, sa, pa + t0 as isize, pa + sa[j as usize] as isize, depth);
|
|
if r <= 0 {
|
|
break;
|
|
}
|
|
loop {
|
|
sa[(j - 1) as usize] = sa[j as usize];
|
|
j += 1;
|
|
if !(j < last && sa[j as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
if last <= j {
|
|
break;
|
|
}
|
|
}
|
|
if r == 0 {
|
|
sa[j as usize] = !sa[j as usize];
|
|
}
|
|
sa[(j - 1) as usize] = t0;
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/// `Td[PA[SA[p]]]` — the depth-`td` sorting key of the suffix stored at `p`.
|
|
#[inline(always)]
|
|
fn ss_key(t: &[u8], sa: &[i32], td: isize, pa: isize, p: isize) -> i32 {
|
|
t[(td + sa[(pa + sa[p as usize] as isize) as usize] as isize) as usize] as i32
|
|
}
|
|
|
|
/// `Td[v]` for an already-loaded SA element `v` (`Td[PA[v]]` in C).
|
|
#[inline(always)]
|
|
fn ss_key_of(t: &[u8], sa: &[i32], td: isize, pa: isize, v: i32) -> i32 {
|
|
t[(td + sa[(pa + v as isize) as usize] as isize) as usize] as i32
|
|
}
|
|
|
|
/// `Td[PA[SA[p]] - 1]` — the character preceding the depth-`td` key.
|
|
#[inline(always)]
|
|
fn ss_key_pred(t: &[u8], sa: &[i32], td: isize, pa: isize, p: isize) -> i32 {
|
|
t[(td + sa[(pa + sa[p as usize] as isize) as usize] as isize - 1) as usize] as i32
|
|
}
|
|
|
|
fn ss_fixdown(t: &[u8], td: isize, sa: &mut [i32], pa: isize, base: isize, i: isize, size: isize) {
|
|
let mut i = i;
|
|
let v = sa[(base + i) as usize];
|
|
let c = ss_key_of(t, sa, td, pa, v);
|
|
loop {
|
|
let mut j = 2 * i + 1;
|
|
if j >= size {
|
|
break;
|
|
}
|
|
let mut k = j;
|
|
j += 1;
|
|
let mut d = ss_key(t, sa, td, pa, base + k);
|
|
let e = ss_key(t, sa, td, pa, base + j);
|
|
if d < e {
|
|
k = j;
|
|
d = e;
|
|
}
|
|
if d <= c {
|
|
break;
|
|
}
|
|
sa[(base + i) as usize] = sa[(base + k) as usize];
|
|
i = k;
|
|
}
|
|
sa[(base + i) as usize] = v;
|
|
}
|
|
|
|
/* Simple top-down heapsort. */
|
|
fn ss_heapsort(t: &[u8], td: isize, sa: &mut [i32], pa: isize, base: isize, size: isize) {
|
|
let mut m = size;
|
|
if size % 2 == 0 {
|
|
m -= 1;
|
|
if ss_key(t, sa, td, pa, base + m / 2) < ss_key(t, sa, td, pa, base + m) {
|
|
sa.swap((base + m) as usize, (base + m / 2) as usize);
|
|
}
|
|
}
|
|
|
|
let mut i = m / 2 - 1;
|
|
while 0 <= i {
|
|
ss_fixdown(t, td, sa, pa, base, i, m);
|
|
i -= 1;
|
|
}
|
|
if size % 2 == 0 {
|
|
sa.swap(base as usize, (base + m) as usize);
|
|
ss_fixdown(t, td, sa, pa, base, 0, m);
|
|
}
|
|
let mut i = m - 1;
|
|
while 0 < i {
|
|
let t0 = sa[base as usize];
|
|
sa[base as usize] = sa[(base + i) as usize];
|
|
ss_fixdown(t, td, sa, pa, base, 0, i);
|
|
sa[(base + i) as usize] = t0;
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Returns the median of three elements. */
|
|
#[inline]
|
|
fn ss_median3(
|
|
t: &[u8],
|
|
sa: &[i32],
|
|
td: isize,
|
|
pa: isize,
|
|
v1: isize,
|
|
v2: isize,
|
|
v3: isize,
|
|
) -> isize {
|
|
let mut v1 = v1;
|
|
let mut v2 = v2;
|
|
if ss_key(t, sa, td, pa, v1) > ss_key(t, sa, td, pa, v2) {
|
|
std::mem::swap(&mut v1, &mut v2);
|
|
}
|
|
if ss_key(t, sa, td, pa, v2) > ss_key(t, sa, td, pa, v3) {
|
|
if ss_key(t, sa, td, pa, v1) > ss_key(t, sa, td, pa, v3) {
|
|
return v1;
|
|
}
|
|
return v3;
|
|
}
|
|
v2
|
|
}
|
|
|
|
/* Returns the median of five elements. */
|
|
#[inline]
|
|
fn ss_median5(
|
|
t: &[u8],
|
|
sa: &[i32],
|
|
td: isize,
|
|
pa: isize,
|
|
v1: isize,
|
|
v2: isize,
|
|
v3: isize,
|
|
v4: isize,
|
|
v5: isize,
|
|
) -> isize {
|
|
let mut v1 = v1;
|
|
let mut v2 = v2;
|
|
let mut v3 = v3;
|
|
let mut v4 = v4;
|
|
let mut v5 = v5;
|
|
if ss_key(t, sa, td, pa, v2) > ss_key(t, sa, td, pa, v3) {
|
|
std::mem::swap(&mut v2, &mut v3);
|
|
}
|
|
if ss_key(t, sa, td, pa, v4) > ss_key(t, sa, td, pa, v5) {
|
|
std::mem::swap(&mut v4, &mut v5);
|
|
}
|
|
if ss_key(t, sa, td, pa, v2) > ss_key(t, sa, td, pa, v4) {
|
|
std::mem::swap(&mut v2, &mut v4);
|
|
std::mem::swap(&mut v3, &mut v5);
|
|
}
|
|
if ss_key(t, sa, td, pa, v1) > ss_key(t, sa, td, pa, v3) {
|
|
std::mem::swap(&mut v1, &mut v3);
|
|
}
|
|
if ss_key(t, sa, td, pa, v1) > ss_key(t, sa, td, pa, v4) {
|
|
std::mem::swap(&mut v1, &mut v4);
|
|
std::mem::swap(&mut v3, &mut v5);
|
|
}
|
|
if ss_key(t, sa, td, pa, v3) > ss_key(t, sa, td, pa, v4) {
|
|
return v4;
|
|
}
|
|
v3
|
|
}
|
|
|
|
/* Returns the pivot element. */
|
|
#[inline]
|
|
fn ss_pivot(t: &[u8], sa: &[i32], td: isize, pa: isize, first: isize, last: isize) -> isize {
|
|
let mut t0 = last - first;
|
|
let middle = first + t0 / 2;
|
|
|
|
if t0 <= 512 {
|
|
if t0 <= 32 {
|
|
return ss_median3(t, sa, td, pa, first, middle, last - 1);
|
|
}
|
|
t0 >>= 2;
|
|
return ss_median5(
|
|
t,
|
|
sa,
|
|
td,
|
|
pa,
|
|
first,
|
|
first + t0,
|
|
middle,
|
|
last - 1 - t0,
|
|
last - 1,
|
|
);
|
|
}
|
|
t0 >>= 3;
|
|
let first = ss_median3(t, sa, td, pa, first, first + t0, first + (t0 << 1));
|
|
let middle = ss_median3(t, sa, td, pa, middle - t0, middle, middle + t0);
|
|
let last = ss_median3(t, sa, td, pa, last - 1 - (t0 << 1), last - 1 - t0, last - 1);
|
|
ss_median3(t, sa, td, pa, first, middle, last)
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Binary partition for substrings. */
|
|
/* The `>= x + 1` comparison deliberately mirrors the C expression shape. */
|
|
#[allow(clippy::int_plus_one)]
|
|
fn ss_partition(sa: &mut [i32], pa: isize, first: isize, last: isize, depth: i32) -> isize {
|
|
let mut a = first - 1;
|
|
let mut b = last;
|
|
loop {
|
|
loop {
|
|
a += 1;
|
|
if !(a < b) {
|
|
break;
|
|
}
|
|
if !(sa[(pa + sa[a as usize] as isize) as usize] + depth
|
|
>= sa[(pa + sa[a as usize] as isize + 1) as usize] + 1)
|
|
{
|
|
break;
|
|
}
|
|
sa[a as usize] = !sa[a as usize];
|
|
}
|
|
loop {
|
|
b -= 1;
|
|
if !(a < b) {
|
|
break;
|
|
}
|
|
if !(sa[(pa + sa[b as usize] as isize) as usize] + depth
|
|
< sa[(pa + sa[b as usize] as isize + 1) as usize] + 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if b <= a {
|
|
break;
|
|
}
|
|
let t0 = !sa[b as usize];
|
|
sa[b as usize] = sa[a as usize];
|
|
sa[a as usize] = t0;
|
|
}
|
|
if first < a {
|
|
sa[first as usize] = !sa[first as usize];
|
|
}
|
|
a
|
|
}
|
|
|
|
/* Multikey introsort for medium size groups. */
|
|
fn ss_mintrosort(t: &[u8], sa: &mut [i32], pa: isize, first: isize, last: isize, depth: i32) {
|
|
let mut stack = [(0isize, 0isize, 0i32, 0i32); SS_MISORT_STACKSIZE];
|
|
let mut ssize = 0usize;
|
|
let mut first = first;
|
|
let mut last = last;
|
|
let mut depth = depth;
|
|
let mut limit = ss_ilg(last - first);
|
|
let mut x: i32 = 0;
|
|
|
|
loop {
|
|
if last - first <= SS_INSERTIONSORT_THRESHOLD {
|
|
if 1 < last - first {
|
|
ss_insertionsort(t, sa, pa, first, last, depth);
|
|
}
|
|
/* STACK_POP */
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(first, last, depth, limit) = stack[ssize];
|
|
continue;
|
|
}
|
|
|
|
let td = depth as isize;
|
|
if limit == 0 {
|
|
ss_heapsort(t, td, sa, pa, first, last - first);
|
|
}
|
|
limit -= 1;
|
|
if limit < 0 {
|
|
let mut a = first + 1;
|
|
let mut v = ss_key(t, sa, td, pa, first);
|
|
while a < last {
|
|
x = ss_key(t, sa, td, pa, a);
|
|
if x != v {
|
|
if 1 < a - first {
|
|
break;
|
|
}
|
|
v = x;
|
|
first = a;
|
|
}
|
|
a += 1;
|
|
}
|
|
if ss_key_pred(t, sa, td, pa, first) < v {
|
|
first = ss_partition(sa, pa, first, a, depth);
|
|
}
|
|
if a - first <= last - a {
|
|
if 1 < a - first {
|
|
stack[ssize] = (a, last, depth, -1);
|
|
ssize += 1;
|
|
last = a;
|
|
depth += 1;
|
|
limit = ss_ilg(a - first);
|
|
} else {
|
|
first = a;
|
|
limit = -1;
|
|
}
|
|
} else if 1 < last - a {
|
|
stack[ssize] = (first, a, depth + 1, ss_ilg(a - first));
|
|
ssize += 1;
|
|
first = a;
|
|
limit = -1;
|
|
} else {
|
|
last = a;
|
|
depth += 1;
|
|
limit = ss_ilg(a - first);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* choose pivot */
|
|
let mut a = ss_pivot(t, sa, td, pa, first, last);
|
|
let v = ss_key(t, sa, td, pa, a);
|
|
sa.swap(first as usize, a as usize);
|
|
|
|
/* partition */
|
|
let mut b = first;
|
|
loop {
|
|
b += 1;
|
|
if !(b < last) {
|
|
break;
|
|
}
|
|
x = ss_key(t, sa, td, pa, b);
|
|
if x != v {
|
|
break;
|
|
}
|
|
}
|
|
a = b;
|
|
if a < last && x < v {
|
|
loop {
|
|
b += 1;
|
|
if !(b < last) {
|
|
break;
|
|
}
|
|
x = ss_key(t, sa, td, pa, b);
|
|
if !(x <= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(b as usize, a as usize);
|
|
a += 1;
|
|
}
|
|
}
|
|
}
|
|
let mut c = last;
|
|
loop {
|
|
c -= 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = ss_key(t, sa, td, pa, c);
|
|
if x != v {
|
|
break;
|
|
}
|
|
}
|
|
let mut d = c;
|
|
if b < d && x > v {
|
|
loop {
|
|
c -= 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = ss_key(t, sa, td, pa, c);
|
|
if !(x >= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(c as usize, d as usize);
|
|
d -= 1;
|
|
}
|
|
}
|
|
}
|
|
while b < c {
|
|
sa.swap(b as usize, c as usize);
|
|
loop {
|
|
b += 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = ss_key(t, sa, td, pa, b);
|
|
if !(x <= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(b as usize, a as usize);
|
|
a += 1;
|
|
}
|
|
}
|
|
loop {
|
|
c -= 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = ss_key(t, sa, td, pa, c);
|
|
if !(x >= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(c as usize, d as usize);
|
|
d -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if a <= d {
|
|
c = b - 1;
|
|
|
|
let mut s = a - first;
|
|
let t0 = b - a;
|
|
if s > t0 {
|
|
s = t0;
|
|
}
|
|
let mut e = first;
|
|
let mut f = b - s;
|
|
while 0 < s {
|
|
sa.swap(e as usize, f as usize);
|
|
s -= 1;
|
|
e += 1;
|
|
f += 1;
|
|
}
|
|
let mut s = d - c;
|
|
let t0 = last - d - 1;
|
|
if s > t0 {
|
|
s = t0;
|
|
}
|
|
let mut e = b;
|
|
let mut f = last - s;
|
|
while 0 < s {
|
|
sa.swap(e as usize, f as usize);
|
|
s -= 1;
|
|
e += 1;
|
|
f += 1;
|
|
}
|
|
|
|
a = first + (b - a);
|
|
c = last - (d - c);
|
|
b = if v <= ss_key_pred(t, sa, td, pa, a) {
|
|
a
|
|
} else {
|
|
ss_partition(sa, pa, a, c, depth)
|
|
};
|
|
|
|
if a - first <= last - c {
|
|
if last - c <= c - b {
|
|
stack[ssize] = (b, c, depth + 1, ss_ilg(c - b));
|
|
ssize += 1;
|
|
stack[ssize] = (c, last, depth, limit);
|
|
ssize += 1;
|
|
last = a;
|
|
} else if a - first <= c - b {
|
|
stack[ssize] = (c, last, depth, limit);
|
|
ssize += 1;
|
|
stack[ssize] = (b, c, depth + 1, ss_ilg(c - b));
|
|
ssize += 1;
|
|
last = a;
|
|
} else {
|
|
stack[ssize] = (c, last, depth, limit);
|
|
ssize += 1;
|
|
stack[ssize] = (first, a, depth, limit);
|
|
ssize += 1;
|
|
first = b;
|
|
last = c;
|
|
depth += 1;
|
|
limit = ss_ilg(c - b);
|
|
}
|
|
} else if a - first <= c - b {
|
|
stack[ssize] = (b, c, depth + 1, ss_ilg(c - b));
|
|
ssize += 1;
|
|
stack[ssize] = (first, a, depth, limit);
|
|
ssize += 1;
|
|
first = c;
|
|
} else if last - c <= c - b {
|
|
stack[ssize] = (first, a, depth, limit);
|
|
ssize += 1;
|
|
stack[ssize] = (b, c, depth + 1, ss_ilg(c - b));
|
|
ssize += 1;
|
|
first = c;
|
|
} else {
|
|
stack[ssize] = (first, a, depth, limit);
|
|
ssize += 1;
|
|
stack[ssize] = (c, last, depth, limit);
|
|
ssize += 1;
|
|
first = b;
|
|
last = c;
|
|
depth += 1;
|
|
limit = ss_ilg(c - b);
|
|
}
|
|
} else {
|
|
limit += 1;
|
|
if ss_key_pred(t, sa, td, pa, first) < v {
|
|
first = ss_partition(sa, pa, first, last, depth);
|
|
limit = ss_ilg(last - first);
|
|
}
|
|
depth += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
#[inline]
|
|
fn ss_blockswap(sa: &mut [i32], a: isize, b: isize, n: isize) {
|
|
let mut a = a;
|
|
let mut b = b;
|
|
let mut n = n;
|
|
while 0 < n {
|
|
sa.swap(a as usize, b as usize);
|
|
n -= 1;
|
|
a += 1;
|
|
b += 1;
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn ss_rotate(sa: &mut [i32], first: isize, middle: isize, last: isize) {
|
|
let mut first = first;
|
|
let mut last = last;
|
|
let mut l = middle - first;
|
|
let mut r = last - middle;
|
|
while 0 < l && 0 < r {
|
|
if l == r {
|
|
ss_blockswap(sa, first, middle, l);
|
|
break;
|
|
}
|
|
if l < r {
|
|
let mut a = last - 1;
|
|
let mut b = middle - 1;
|
|
let mut t0 = sa[a as usize];
|
|
loop {
|
|
sa[a as usize] = sa[b as usize];
|
|
a -= 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
if b < first {
|
|
sa[a as usize] = t0;
|
|
last = a;
|
|
r -= l + 1;
|
|
if r <= l {
|
|
break;
|
|
}
|
|
a -= 1;
|
|
b = middle - 1;
|
|
t0 = sa[a as usize];
|
|
}
|
|
}
|
|
} else {
|
|
let mut a = first;
|
|
let mut b = middle;
|
|
let mut t0 = sa[a as usize];
|
|
loop {
|
|
sa[a as usize] = sa[b as usize];
|
|
a += 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b += 1;
|
|
if last <= b {
|
|
sa[a as usize] = t0;
|
|
first = a + 1;
|
|
l -= r + 1;
|
|
if l <= r {
|
|
break;
|
|
}
|
|
a += 1;
|
|
b = middle;
|
|
t0 = sa[a as usize];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
fn ss_inplacemerge(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
pa: isize,
|
|
first: isize,
|
|
middle: isize,
|
|
last: isize,
|
|
depth: i32,
|
|
) {
|
|
let mut middle = middle;
|
|
let mut last = last;
|
|
loop {
|
|
let x: i32;
|
|
let p: isize;
|
|
if sa[(last - 1) as usize] < 0 {
|
|
x = 1;
|
|
p = pa + (!sa[(last - 1) as usize]) as isize;
|
|
} else {
|
|
x = 0;
|
|
p = pa + sa[(last - 1) as usize] as isize;
|
|
}
|
|
let mut a = first;
|
|
let mut len = middle - first;
|
|
let mut half = len >> 1;
|
|
let mut r: i32 = -1;
|
|
while 0 < len {
|
|
let b = a + half;
|
|
let bv = sa[b as usize];
|
|
let q = ss_compare_pa(
|
|
t,
|
|
sa,
|
|
pa + (if 0 <= bv { bv } else { !bv }) as isize,
|
|
p,
|
|
depth,
|
|
);
|
|
if q < 0 {
|
|
a = b + 1;
|
|
half -= (len & 1) ^ 1;
|
|
} else {
|
|
r = q;
|
|
}
|
|
len = half;
|
|
half >>= 1;
|
|
}
|
|
if a < middle {
|
|
if r == 0 {
|
|
sa[a as usize] = !sa[a as usize];
|
|
}
|
|
ss_rotate(sa, a, middle, last);
|
|
last -= middle - a;
|
|
middle = a;
|
|
if first == middle {
|
|
break;
|
|
}
|
|
}
|
|
last -= 1;
|
|
if x != 0 {
|
|
loop {
|
|
last -= 1;
|
|
if !(sa[last as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if middle == last {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Merge-forward with internal buffer. */
|
|
fn ss_mergeforward(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
pa: isize,
|
|
first: isize,
|
|
middle: isize,
|
|
last: isize,
|
|
buf: isize,
|
|
depth: i32,
|
|
) {
|
|
let bufend = buf + (middle - first) - 1;
|
|
ss_blockswap(sa, buf, first, middle - first);
|
|
|
|
let mut a = first;
|
|
let t0 = sa[a as usize];
|
|
let mut b = buf;
|
|
let mut c = middle;
|
|
loop {
|
|
let r = ss_compare_pa(
|
|
t,
|
|
sa,
|
|
pa + sa[b as usize] as isize,
|
|
pa + sa[c as usize] as isize,
|
|
depth,
|
|
);
|
|
if r < 0 {
|
|
loop {
|
|
sa[a as usize] = sa[b as usize];
|
|
a += 1;
|
|
if bufend <= b {
|
|
sa[bufend as usize] = t0;
|
|
return;
|
|
}
|
|
sa[b as usize] = sa[a as usize];
|
|
b += 1;
|
|
if !(sa[b as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
} else if r > 0 {
|
|
loop {
|
|
sa[a as usize] = sa[c as usize];
|
|
a += 1;
|
|
sa[c as usize] = sa[a as usize];
|
|
c += 1;
|
|
if last <= c {
|
|
while b < bufend {
|
|
sa[a as usize] = sa[b as usize];
|
|
a += 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b += 1;
|
|
}
|
|
sa[a as usize] = sa[b as usize];
|
|
sa[b as usize] = t0;
|
|
return;
|
|
}
|
|
if !(sa[c as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
sa[c as usize] = !sa[c as usize];
|
|
loop {
|
|
sa[a as usize] = sa[b as usize];
|
|
a += 1;
|
|
if bufend <= b {
|
|
sa[bufend as usize] = t0;
|
|
return;
|
|
}
|
|
sa[b as usize] = sa[a as usize];
|
|
b += 1;
|
|
if !(sa[b as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
loop {
|
|
sa[a as usize] = sa[c as usize];
|
|
a += 1;
|
|
sa[c as usize] = sa[a as usize];
|
|
c += 1;
|
|
if last <= c {
|
|
while b < bufend {
|
|
sa[a as usize] = sa[b as usize];
|
|
a += 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b += 1;
|
|
}
|
|
sa[a as usize] = sa[b as usize];
|
|
sa[b as usize] = t0;
|
|
return;
|
|
}
|
|
if !(sa[c as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Merge-backward with internal buffer. */
|
|
fn ss_mergebackward(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
pa: isize,
|
|
first: isize,
|
|
middle: isize,
|
|
last: isize,
|
|
buf: isize,
|
|
depth: i32,
|
|
) {
|
|
let bufend = buf + (last - middle) - 1;
|
|
ss_blockswap(sa, buf, middle, last - middle);
|
|
|
|
let mut x = 0i32;
|
|
let mut p1: isize;
|
|
let mut p2: isize;
|
|
if sa[bufend as usize] < 0 {
|
|
p1 = pa + (!sa[bufend as usize]) as isize;
|
|
x |= 1;
|
|
} else {
|
|
p1 = pa + sa[bufend as usize] as isize;
|
|
}
|
|
if sa[(middle - 1) as usize] < 0 {
|
|
p2 = pa + (!sa[(middle - 1) as usize]) as isize;
|
|
x |= 2;
|
|
} else {
|
|
p2 = pa + sa[(middle - 1) as usize] as isize;
|
|
}
|
|
let mut a = last - 1;
|
|
let t0 = sa[a as usize];
|
|
let mut b = bufend;
|
|
let mut c = middle - 1;
|
|
loop {
|
|
let r = ss_compare_pa(t, sa, p1, p2, depth);
|
|
if 0 < r {
|
|
if x & 1 != 0 {
|
|
loop {
|
|
sa[a as usize] = sa[b as usize];
|
|
a -= 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
if !(sa[b as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
x ^= 1;
|
|
}
|
|
sa[a as usize] = sa[b as usize];
|
|
a -= 1;
|
|
if b <= buf {
|
|
sa[buf as usize] = t0;
|
|
break;
|
|
}
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
if sa[b as usize] < 0 {
|
|
p1 = pa + (!sa[b as usize]) as isize;
|
|
x |= 1;
|
|
} else {
|
|
p1 = pa + sa[b as usize] as isize;
|
|
}
|
|
} else if r < 0 {
|
|
if x & 2 != 0 {
|
|
loop {
|
|
sa[a as usize] = sa[c as usize];
|
|
a -= 1;
|
|
sa[c as usize] = sa[a as usize];
|
|
c -= 1;
|
|
if !(sa[c as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
x ^= 2;
|
|
}
|
|
sa[a as usize] = sa[c as usize];
|
|
a -= 1;
|
|
sa[c as usize] = sa[a as usize];
|
|
c -= 1;
|
|
if c < first {
|
|
while buf < b {
|
|
sa[a as usize] = sa[b as usize];
|
|
a -= 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
}
|
|
sa[a as usize] = sa[b as usize];
|
|
sa[b as usize] = t0;
|
|
break;
|
|
}
|
|
if sa[c as usize] < 0 {
|
|
p2 = pa + (!sa[c as usize]) as isize;
|
|
x |= 2;
|
|
} else {
|
|
p2 = pa + sa[c as usize] as isize;
|
|
}
|
|
} else {
|
|
if x & 1 != 0 {
|
|
loop {
|
|
sa[a as usize] = sa[b as usize];
|
|
a -= 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
if !(sa[b as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
x ^= 1;
|
|
}
|
|
sa[a as usize] = !sa[b as usize];
|
|
a -= 1;
|
|
if b <= buf {
|
|
sa[buf as usize] = t0;
|
|
break;
|
|
}
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
if x & 2 != 0 {
|
|
loop {
|
|
sa[a as usize] = sa[c as usize];
|
|
a -= 1;
|
|
sa[c as usize] = sa[a as usize];
|
|
c -= 1;
|
|
if !(sa[c as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
x ^= 2;
|
|
}
|
|
sa[a as usize] = sa[c as usize];
|
|
a -= 1;
|
|
sa[c as usize] = sa[a as usize];
|
|
c -= 1;
|
|
if c < first {
|
|
while buf < b {
|
|
sa[a as usize] = sa[b as usize];
|
|
a -= 1;
|
|
sa[b as usize] = sa[a as usize];
|
|
b -= 1;
|
|
}
|
|
sa[a as usize] = sa[b as usize];
|
|
sa[b as usize] = t0;
|
|
break;
|
|
}
|
|
if sa[b as usize] < 0 {
|
|
p1 = pa + (!sa[b as usize]) as isize;
|
|
x |= 1;
|
|
} else {
|
|
p1 = pa + sa[b as usize] as isize;
|
|
}
|
|
if sa[c as usize] < 0 {
|
|
p2 = pa + (!sa[c as usize]) as isize;
|
|
x |= 2;
|
|
} else {
|
|
p2 = pa + sa[c as usize] as isize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `GETIDX` — undoes the "already merged" complement marking.
|
|
#[inline(always)]
|
|
fn getidx(a: i32) -> i32 {
|
|
if 0 <= a {
|
|
a
|
|
} else {
|
|
!a
|
|
}
|
|
}
|
|
|
|
/// `MERGE_CHECK` — restores or sets the complement marks after a merge.
|
|
#[inline]
|
|
fn ss_merge_check(t: &[u8], sa: &mut [i32], pa: isize, a: isize, b: isize, c: i32, depth: i32) {
|
|
if (c & 1) != 0
|
|
|| ((c & 2) != 0
|
|
&& ss_compare_pa(
|
|
t,
|
|
sa,
|
|
pa + getidx(sa[(a - 1) as usize]) as isize,
|
|
pa + sa[a as usize] as isize,
|
|
depth,
|
|
) == 0)
|
|
{
|
|
sa[a as usize] = !sa[a as usize];
|
|
}
|
|
if (c & 4) != 0
|
|
&& ss_compare_pa(
|
|
t,
|
|
sa,
|
|
pa + getidx(sa[(b - 1) as usize]) as isize,
|
|
pa + sa[b as usize] as isize,
|
|
depth,
|
|
) == 0
|
|
{
|
|
sa[b as usize] = !sa[b as usize];
|
|
}
|
|
}
|
|
|
|
/* D&C based merge. */
|
|
fn ss_swapmerge(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
pa: isize,
|
|
first: isize,
|
|
middle: isize,
|
|
last: isize,
|
|
buf: isize,
|
|
bufsize: isize,
|
|
depth: i32,
|
|
) {
|
|
let mut stack = [(0isize, 0isize, 0isize, 0i32); SS_SMERGE_STACKSIZE];
|
|
let mut ssize = 0usize;
|
|
let mut first = first;
|
|
let mut middle = middle;
|
|
let mut last = last;
|
|
let mut check = 0i32;
|
|
|
|
loop {
|
|
if last - middle <= bufsize {
|
|
if first < middle && middle < last {
|
|
ss_mergebackward(t, sa, pa, first, middle, last, buf, depth);
|
|
}
|
|
ss_merge_check(t, sa, pa, first, last, check, depth);
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(first, middle, last, check) = stack[ssize];
|
|
continue;
|
|
}
|
|
|
|
if middle - first <= bufsize {
|
|
if first < middle {
|
|
ss_mergeforward(t, sa, pa, first, middle, last, buf, depth);
|
|
}
|
|
ss_merge_check(t, sa, pa, first, last, check, depth);
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(first, middle, last, check) = stack[ssize];
|
|
continue;
|
|
}
|
|
|
|
let mut m: isize = 0;
|
|
let mut len = std::cmp::min(middle - first, last - middle);
|
|
let mut half = len >> 1;
|
|
while 0 < len {
|
|
if ss_compare_pa(
|
|
t,
|
|
sa,
|
|
pa + getidx(sa[(middle + m + half) as usize]) as isize,
|
|
pa + getidx(sa[(middle - m - half - 1) as usize]) as isize,
|
|
depth,
|
|
) < 0
|
|
{
|
|
m += half + 1;
|
|
half -= (len & 1) ^ 1;
|
|
}
|
|
len = half;
|
|
half >>= 1;
|
|
}
|
|
|
|
if 0 < m {
|
|
let lm = middle - m;
|
|
let rm = middle + m;
|
|
ss_blockswap(sa, lm, middle, m);
|
|
let mut l = middle;
|
|
let mut r = middle;
|
|
let mut next = 0i32;
|
|
if rm < last {
|
|
if sa[rm as usize] < 0 {
|
|
sa[rm as usize] = !sa[rm as usize];
|
|
if first < lm {
|
|
loop {
|
|
l -= 1;
|
|
if !(sa[l as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
next |= 4;
|
|
}
|
|
next |= 1;
|
|
} else if first < lm {
|
|
while sa[r as usize] < 0 {
|
|
r += 1;
|
|
}
|
|
next |= 2;
|
|
}
|
|
}
|
|
|
|
if l - first <= last - r {
|
|
stack[ssize] = (r, rm, last, (next & 3) | (check & 4));
|
|
ssize += 1;
|
|
middle = lm;
|
|
last = l;
|
|
check = (check & 3) | (next & 4);
|
|
} else {
|
|
if (next & 2) != 0 && r == middle {
|
|
next ^= 6;
|
|
}
|
|
stack[ssize] = (first, lm, l, (check & 3) | (next & 4));
|
|
ssize += 1;
|
|
first = r;
|
|
middle = rm;
|
|
check = (next & 3) | (check & 4);
|
|
}
|
|
} else {
|
|
if ss_compare_pa(
|
|
t,
|
|
sa,
|
|
pa + getidx(sa[(middle - 1) as usize]) as isize,
|
|
pa + sa[middle as usize] as isize,
|
|
depth,
|
|
) == 0
|
|
{
|
|
sa[middle as usize] = !sa[middle as usize];
|
|
}
|
|
ss_merge_check(t, sa, pa, first, last, check, depth);
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(first, middle, last, check) = stack[ssize];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Substring sort */
|
|
fn sssort(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
pa: isize,
|
|
first: isize,
|
|
last: isize,
|
|
buf: isize,
|
|
bufsize: isize,
|
|
depth: i32,
|
|
n: isize,
|
|
lastsuffix: bool,
|
|
) {
|
|
let mut first = first;
|
|
let mut buf = buf;
|
|
let mut bufsize = bufsize;
|
|
|
|
if lastsuffix {
|
|
first += 1;
|
|
}
|
|
|
|
let mut limit: isize = 0;
|
|
let mut middle = last;
|
|
if bufsize < SS_BLOCKSIZE && bufsize < last - first {
|
|
limit = ss_isqrt(last - first);
|
|
if bufsize < limit {
|
|
if SS_BLOCKSIZE < limit {
|
|
limit = SS_BLOCKSIZE;
|
|
}
|
|
middle = last - limit;
|
|
buf = middle;
|
|
bufsize = limit;
|
|
} else {
|
|
limit = 0;
|
|
}
|
|
}
|
|
let mut a = first;
|
|
let mut i: isize = 0;
|
|
while SS_BLOCKSIZE < middle - a {
|
|
ss_mintrosort(t, sa, pa, a, a + SS_BLOCKSIZE, depth);
|
|
let mut curbufsize = last - (a + SS_BLOCKSIZE);
|
|
let mut curbuf = a + SS_BLOCKSIZE;
|
|
if curbufsize <= bufsize {
|
|
curbufsize = bufsize;
|
|
curbuf = buf;
|
|
}
|
|
let mut b = a;
|
|
let mut k = SS_BLOCKSIZE;
|
|
let mut j = i;
|
|
while j & 1 != 0 {
|
|
ss_swapmerge(t, sa, pa, b - k, b, b + k, curbuf, curbufsize, depth);
|
|
b -= k;
|
|
k <<= 1;
|
|
j >>= 1;
|
|
}
|
|
a += SS_BLOCKSIZE;
|
|
i += 1;
|
|
}
|
|
ss_mintrosort(t, sa, pa, a, middle, depth);
|
|
let mut k = SS_BLOCKSIZE;
|
|
while i != 0 {
|
|
if i & 1 != 0 {
|
|
ss_swapmerge(t, sa, pa, a - k, a, middle, buf, bufsize, depth);
|
|
a -= k;
|
|
}
|
|
k <<= 1;
|
|
i >>= 1;
|
|
}
|
|
if limit != 0 {
|
|
ss_mintrosort(t, sa, pa, middle, last, depth);
|
|
ss_inplacemerge(t, sa, pa, first, middle, last, depth);
|
|
}
|
|
|
|
if lastsuffix {
|
|
/* Insert last type B* suffix. */
|
|
let pai0 = sa[(pa + sa[(first - 1) as usize] as isize) as usize];
|
|
let pai1 = (n - 2) as i32;
|
|
let i0 = sa[(first - 1) as usize];
|
|
let mut a = first;
|
|
while a < last {
|
|
let av = sa[a as usize];
|
|
if !(av < 0
|
|
|| 0 < ss_compare(
|
|
t,
|
|
pai0,
|
|
pai1,
|
|
sa[(pa + av as isize) as usize],
|
|
sa[(pa + av as isize + 1) as usize],
|
|
depth,
|
|
))
|
|
{
|
|
break;
|
|
}
|
|
sa[(a - 1) as usize] = av;
|
|
a += 1;
|
|
}
|
|
sa[(a - 1) as usize] = i0;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
#[inline]
|
|
fn tr_ilg(n: isize) -> i32 {
|
|
let n = n as i32;
|
|
if (n as u32) & 0xffff_0000 != 0 {
|
|
if (n as u32) & 0xff00_0000 != 0 {
|
|
24 + LG_TABLE[((n >> 24) & 0xff) as usize]
|
|
} else {
|
|
16 + LG_TABLE[((n >> 16) & 0xff) as usize]
|
|
}
|
|
} else if n & 0xff00 != 0 {
|
|
8 + LG_TABLE[((n >> 8) & 0xff) as usize]
|
|
} else {
|
|
LG_TABLE[(n & 0xff) as usize]
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/// `ISAd[SA[p]]` — the depth-offset rank of the suffix stored at `p`.
|
|
#[inline(always)]
|
|
fn tr_key(sa: &[i32], isad: isize, p: isize) -> i32 {
|
|
sa[(isad + sa[p as usize] as isize) as usize]
|
|
}
|
|
|
|
/* Simple insertionsort for small size groups. */
|
|
fn tr_insertionsort(sa: &mut [i32], isad: isize, first: isize, last: isize) {
|
|
let mut a = first + 1;
|
|
while a < last {
|
|
let t0 = sa[a as usize];
|
|
let mut b = a - 1;
|
|
let mut r;
|
|
loop {
|
|
r = sa[(isad + t0 as isize) as usize] - tr_key(sa, isad, b);
|
|
if !(0 > r) {
|
|
break;
|
|
}
|
|
loop {
|
|
sa[(b + 1) as usize] = sa[b as usize];
|
|
b -= 1;
|
|
if !(first <= b && sa[b as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
if b < first {
|
|
break;
|
|
}
|
|
}
|
|
if r == 0 {
|
|
sa[b as usize] = !sa[b as usize];
|
|
}
|
|
sa[(b + 1) as usize] = t0;
|
|
a += 1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
fn tr_fixdown(sa: &mut [i32], isad: isize, base: isize, i: isize, size: isize) {
|
|
let mut i = i;
|
|
let v = sa[(base + i) as usize];
|
|
let c = sa[(isad + v as isize) as usize];
|
|
loop {
|
|
let mut j = 2 * i + 1;
|
|
if j >= size {
|
|
break;
|
|
}
|
|
let mut k = j;
|
|
j += 1;
|
|
let mut d = tr_key(sa, isad, base + k);
|
|
let e = tr_key(sa, isad, base + j);
|
|
if d < e {
|
|
k = j;
|
|
d = e;
|
|
}
|
|
if d <= c {
|
|
break;
|
|
}
|
|
sa[(base + i) as usize] = sa[(base + k) as usize];
|
|
i = k;
|
|
}
|
|
sa[(base + i) as usize] = v;
|
|
}
|
|
|
|
/* Simple top-down heapsort. */
|
|
fn tr_heapsort(sa: &mut [i32], isad: isize, base: isize, size: isize) {
|
|
let mut m = size;
|
|
if size % 2 == 0 {
|
|
m -= 1;
|
|
if tr_key(sa, isad, base + m / 2) < tr_key(sa, isad, base + m) {
|
|
sa.swap((base + m) as usize, (base + m / 2) as usize);
|
|
}
|
|
}
|
|
|
|
let mut i = m / 2 - 1;
|
|
while 0 <= i {
|
|
tr_fixdown(sa, isad, base, i, m);
|
|
i -= 1;
|
|
}
|
|
if size % 2 == 0 {
|
|
sa.swap(base as usize, (base + m) as usize);
|
|
tr_fixdown(sa, isad, base, 0, m);
|
|
}
|
|
let mut i = m - 1;
|
|
while 0 < i {
|
|
let t0 = sa[base as usize];
|
|
sa[base as usize] = sa[(base + i) as usize];
|
|
tr_fixdown(sa, isad, base, 0, i);
|
|
sa[(base + i) as usize] = t0;
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Returns the median of three elements. */
|
|
#[inline]
|
|
fn tr_median3(sa: &[i32], isad: isize, v1: isize, v2: isize, v3: isize) -> isize {
|
|
let mut v1 = v1;
|
|
let mut v2 = v2;
|
|
if tr_key(sa, isad, v1) > tr_key(sa, isad, v2) {
|
|
std::mem::swap(&mut v1, &mut v2);
|
|
}
|
|
if tr_key(sa, isad, v2) > tr_key(sa, isad, v3) {
|
|
if tr_key(sa, isad, v1) > tr_key(sa, isad, v3) {
|
|
return v1;
|
|
}
|
|
return v3;
|
|
}
|
|
v2
|
|
}
|
|
|
|
/* Returns the median of five elements. */
|
|
#[inline]
|
|
fn tr_median5(
|
|
sa: &[i32],
|
|
isad: isize,
|
|
v1: isize,
|
|
v2: isize,
|
|
v3: isize,
|
|
v4: isize,
|
|
v5: isize,
|
|
) -> isize {
|
|
let mut v1 = v1;
|
|
let mut v2 = v2;
|
|
let mut v3 = v3;
|
|
let mut v4 = v4;
|
|
let mut v5 = v5;
|
|
if tr_key(sa, isad, v2) > tr_key(sa, isad, v3) {
|
|
std::mem::swap(&mut v2, &mut v3);
|
|
}
|
|
if tr_key(sa, isad, v4) > tr_key(sa, isad, v5) {
|
|
std::mem::swap(&mut v4, &mut v5);
|
|
}
|
|
if tr_key(sa, isad, v2) > tr_key(sa, isad, v4) {
|
|
std::mem::swap(&mut v2, &mut v4);
|
|
std::mem::swap(&mut v3, &mut v5);
|
|
}
|
|
if tr_key(sa, isad, v1) > tr_key(sa, isad, v3) {
|
|
std::mem::swap(&mut v1, &mut v3);
|
|
}
|
|
if tr_key(sa, isad, v1) > tr_key(sa, isad, v4) {
|
|
std::mem::swap(&mut v1, &mut v4);
|
|
std::mem::swap(&mut v3, &mut v5);
|
|
}
|
|
if tr_key(sa, isad, v3) > tr_key(sa, isad, v4) {
|
|
return v4;
|
|
}
|
|
v3
|
|
}
|
|
|
|
/* Returns the pivot element. */
|
|
#[inline]
|
|
fn tr_pivot(sa: &[i32], isad: isize, first: isize, last: isize) -> isize {
|
|
let mut t0 = last - first;
|
|
let middle = first + t0 / 2;
|
|
|
|
if t0 <= 512 {
|
|
if t0 <= 32 {
|
|
return tr_median3(sa, isad, first, middle, last - 1);
|
|
}
|
|
t0 >>= 2;
|
|
return tr_median5(sa, isad, first, first + t0, middle, last - 1 - t0, last - 1);
|
|
}
|
|
t0 >>= 3;
|
|
let first = tr_median3(sa, isad, first, first + t0, first + (t0 << 1));
|
|
let middle = tr_median3(sa, isad, middle - t0, middle, middle + t0);
|
|
let last = tr_median3(sa, isad, last - 1 - (t0 << 1), last - 1 - t0, last - 1);
|
|
tr_median3(sa, isad, first, middle, last)
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
struct TrBudget {
|
|
chance: i32,
|
|
remain: i32,
|
|
incval: i32,
|
|
count: i32,
|
|
}
|
|
|
|
impl TrBudget {
|
|
fn new(chance: i32, incval: i32) -> Self {
|
|
TrBudget {
|
|
chance,
|
|
remain: incval,
|
|
incval,
|
|
count: 0,
|
|
}
|
|
}
|
|
|
|
fn check(&mut self, size: isize) -> bool {
|
|
let size = size as i32;
|
|
if size <= self.remain {
|
|
self.remain -= size;
|
|
return true;
|
|
}
|
|
if self.chance == 0 {
|
|
self.count += size;
|
|
return false;
|
|
}
|
|
self.remain += self.incval - size;
|
|
self.chance -= 1;
|
|
true
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
fn tr_partition(
|
|
sa: &mut [i32],
|
|
isad: isize,
|
|
first: isize,
|
|
middle: isize,
|
|
last: isize,
|
|
v: i32,
|
|
) -> (isize, isize) {
|
|
let mut first = first;
|
|
let mut last = last;
|
|
let mut x: i32 = 0;
|
|
|
|
let mut b = middle - 1;
|
|
loop {
|
|
b += 1;
|
|
if !(b < last) {
|
|
break;
|
|
}
|
|
x = tr_key(sa, isad, b);
|
|
if x != v {
|
|
break;
|
|
}
|
|
}
|
|
let mut a = b;
|
|
if a < last && x < v {
|
|
loop {
|
|
b += 1;
|
|
if !(b < last) {
|
|
break;
|
|
}
|
|
x = tr_key(sa, isad, b);
|
|
if !(x <= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(b as usize, a as usize);
|
|
a += 1;
|
|
}
|
|
}
|
|
}
|
|
let mut c = last;
|
|
loop {
|
|
c -= 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = tr_key(sa, isad, c);
|
|
if x != v {
|
|
break;
|
|
}
|
|
}
|
|
let mut d = c;
|
|
if b < d && x > v {
|
|
loop {
|
|
c -= 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = tr_key(sa, isad, c);
|
|
if !(x >= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(c as usize, d as usize);
|
|
d -= 1;
|
|
}
|
|
}
|
|
}
|
|
while b < c {
|
|
sa.swap(b as usize, c as usize);
|
|
loop {
|
|
b += 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = tr_key(sa, isad, b);
|
|
if !(x <= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(b as usize, a as usize);
|
|
a += 1;
|
|
}
|
|
}
|
|
loop {
|
|
c -= 1;
|
|
if !(b < c) {
|
|
break;
|
|
}
|
|
x = tr_key(sa, isad, c);
|
|
if !(x >= v) {
|
|
break;
|
|
}
|
|
if x == v {
|
|
sa.swap(c as usize, d as usize);
|
|
d -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if a <= d {
|
|
c = b - 1;
|
|
let mut s = a - first;
|
|
let t0 = b - a;
|
|
if s > t0 {
|
|
s = t0;
|
|
}
|
|
let mut e = first;
|
|
let mut f = b - s;
|
|
while 0 < s {
|
|
sa.swap(e as usize, f as usize);
|
|
s -= 1;
|
|
e += 1;
|
|
f += 1;
|
|
}
|
|
let mut s = d - c;
|
|
let t0 = last - d - 1;
|
|
if s > t0 {
|
|
s = t0;
|
|
}
|
|
let mut e = b;
|
|
let mut f = last - s;
|
|
while 0 < s {
|
|
sa.swap(e as usize, f as usize);
|
|
s -= 1;
|
|
e += 1;
|
|
f += 1;
|
|
}
|
|
first += b - a;
|
|
last -= d - c;
|
|
}
|
|
(first, last)
|
|
}
|
|
|
|
/* sort suffixes of middle partition by using sorted order of suffixes of
|
|
* left and right partition. */
|
|
fn tr_copy(
|
|
sa: &mut [i32],
|
|
isa: isize,
|
|
first: isize,
|
|
a: isize,
|
|
b: isize,
|
|
last: isize,
|
|
depth: isize,
|
|
) {
|
|
/* All cursor arithmetic is relative to the slice start, which is the C
|
|
* routine's `SA` pointer, so `x - SA` becomes plain `x`. */
|
|
let v = (b - 1) as i32;
|
|
|
|
let mut c = first;
|
|
let mut d = a - 1;
|
|
while c <= d {
|
|
let s = sa[c as usize] - depth as i32;
|
|
if 0 <= s && sa[(isa + s as isize) as usize] == v {
|
|
d += 1;
|
|
sa[d as usize] = s;
|
|
sa[(isa + s as isize) as usize] = d as i32;
|
|
}
|
|
c += 1;
|
|
}
|
|
let mut c = last - 1;
|
|
let e = d + 1;
|
|
let mut d = b;
|
|
while e < d {
|
|
let s = sa[c as usize] - depth as i32;
|
|
if 0 <= s && sa[(isa + s as isize) as usize] == v {
|
|
d -= 1;
|
|
sa[d as usize] = s;
|
|
sa[(isa + s as isize) as usize] = d as i32;
|
|
}
|
|
c -= 1;
|
|
}
|
|
}
|
|
|
|
fn tr_partialcopy(
|
|
sa: &mut [i32],
|
|
isa: isize,
|
|
first: isize,
|
|
a: isize,
|
|
b: isize,
|
|
last: isize,
|
|
depth: isize,
|
|
) {
|
|
let v = (b - 1) as i32;
|
|
let mut newrank: i32 = -1;
|
|
|
|
let mut lastrank: i32 = -1;
|
|
let mut c = first;
|
|
let mut d = a - 1;
|
|
while c <= d {
|
|
let s = sa[c as usize] - depth as i32;
|
|
if 0 <= s && sa[(isa + s as isize) as usize] == v {
|
|
d += 1;
|
|
sa[d as usize] = s;
|
|
let rank = sa[(isa + s as isize + depth) as usize];
|
|
if lastrank != rank {
|
|
lastrank = rank;
|
|
newrank = d as i32;
|
|
}
|
|
sa[(isa + s as isize) as usize] = newrank;
|
|
}
|
|
c += 1;
|
|
}
|
|
|
|
let mut lastrank: i32 = -1;
|
|
let mut e = d;
|
|
while first <= e {
|
|
let rank = sa[(isa + sa[e as usize] as isize) as usize];
|
|
if lastrank != rank {
|
|
lastrank = rank;
|
|
newrank = e as i32;
|
|
}
|
|
if newrank != rank {
|
|
sa[(isa + sa[e as usize] as isize) as usize] = newrank;
|
|
}
|
|
e -= 1;
|
|
}
|
|
|
|
let mut lastrank: i32 = -1;
|
|
let mut c = last - 1;
|
|
let e = d + 1;
|
|
let mut d = b;
|
|
while e < d {
|
|
let s = sa[c as usize] - depth as i32;
|
|
if 0 <= s && sa[(isa + s as isize) as usize] == v {
|
|
d -= 1;
|
|
sa[d as usize] = s;
|
|
let rank = sa[(isa + s as isize + depth) as usize];
|
|
if lastrank != rank {
|
|
lastrank = rank;
|
|
newrank = d as i32;
|
|
}
|
|
sa[(isa + s as isize) as usize] = newrank;
|
|
}
|
|
c -= 1;
|
|
}
|
|
}
|
|
|
|
fn tr_introsort(
|
|
sa: &mut [i32],
|
|
isa: isize,
|
|
isad: isize,
|
|
first: isize,
|
|
last: isize,
|
|
budget: &mut TrBudget,
|
|
) {
|
|
/* Stack frames are (ISAd, first, last, limit, trlink); the tandem-repeat
|
|
* copy frame stores its `(a, b)` pair in the pointer fields with a zero
|
|
* placeholder where C pushes a NULL ISAd. */
|
|
let mut stack = [(0isize, 0isize, 0isize, 0i32, 0i32); TR_STACKSIZE];
|
|
let mut ssize = 0usize;
|
|
let mut trlink: i32 = -1;
|
|
let mut isad = isad;
|
|
let mut first = first;
|
|
let mut last = last;
|
|
let incr = isad - isa;
|
|
let mut limit = tr_ilg(last - first);
|
|
|
|
loop {
|
|
if limit < 0 {
|
|
if limit == -1 {
|
|
/* tandem repeat partition */
|
|
let (a, b) = tr_partition(sa, isad - incr, first, first, last, (last - 1) as i32);
|
|
|
|
/* update ranks */
|
|
if a < last {
|
|
let v = (a - 1) as i32;
|
|
let mut c = first;
|
|
while c < a {
|
|
sa[(isa + sa[c as usize] as isize) as usize] = v;
|
|
c += 1;
|
|
}
|
|
}
|
|
if b < last {
|
|
let v = (b - 1) as i32;
|
|
let mut c = a;
|
|
while c < b {
|
|
sa[(isa + sa[c as usize] as isize) as usize] = v;
|
|
c += 1;
|
|
}
|
|
}
|
|
|
|
/* push */
|
|
if 1 < b - a {
|
|
stack[ssize] = (0, a, b, 0, 0);
|
|
ssize += 1;
|
|
stack[ssize] = (isad - incr, first, last, -2, trlink);
|
|
ssize += 1;
|
|
trlink = ssize as i32 - 2;
|
|
}
|
|
if a - first <= last - b {
|
|
if 1 < a - first {
|
|
stack[ssize] = (isad, b, last, tr_ilg(last - b), trlink);
|
|
ssize += 1;
|
|
last = a;
|
|
limit = tr_ilg(a - first);
|
|
} else if 1 < last - b {
|
|
first = b;
|
|
limit = tr_ilg(last - b);
|
|
} else {
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
} else if 1 < last - b {
|
|
stack[ssize] = (isad, first, a, tr_ilg(a - first), trlink);
|
|
ssize += 1;
|
|
first = b;
|
|
limit = tr_ilg(last - b);
|
|
} else if 1 < a - first {
|
|
last = a;
|
|
limit = tr_ilg(a - first);
|
|
} else {
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
} else if limit == -2 {
|
|
/* tandem repeat copy */
|
|
ssize -= 1;
|
|
let a = stack[ssize].1;
|
|
let b = stack[ssize].2;
|
|
if stack[ssize].3 == 0 {
|
|
tr_copy(sa, isa, first, a, b, last, isad - isa);
|
|
} else {
|
|
if 0 <= trlink {
|
|
stack[trlink as usize].3 = -1;
|
|
}
|
|
tr_partialcopy(sa, isa, first, a, b, last, isad - isa);
|
|
}
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
} else {
|
|
/* sorted partition */
|
|
if 0 <= sa[first as usize] {
|
|
let mut a = first;
|
|
loop {
|
|
sa[(isa + sa[a as usize] as isize) as usize] = a as i32;
|
|
a += 1;
|
|
if !(a < last && 0 <= sa[a as usize]) {
|
|
break;
|
|
}
|
|
}
|
|
first = a;
|
|
}
|
|
if first < last {
|
|
let mut a = first;
|
|
loop {
|
|
sa[a as usize] = !sa[a as usize];
|
|
a += 1;
|
|
if !(sa[a as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
let next =
|
|
if sa[(isa + sa[a as usize] as isize) as usize] != tr_key(sa, isad, a) {
|
|
tr_ilg(a - first + 1)
|
|
} else {
|
|
-1
|
|
};
|
|
a += 1;
|
|
if a < last {
|
|
let v = (a - 1) as i32;
|
|
let mut b = first;
|
|
while b < a {
|
|
sa[(isa + sa[b as usize] as isize) as usize] = v;
|
|
b += 1;
|
|
}
|
|
}
|
|
|
|
/* push */
|
|
if budget.check(a - first) {
|
|
if a - first <= last - a {
|
|
stack[ssize] = (isad, a, last, -3, trlink);
|
|
ssize += 1;
|
|
isad += incr;
|
|
last = a;
|
|
limit = next;
|
|
} else if 1 < last - a {
|
|
stack[ssize] = (isad + incr, first, a, next, trlink);
|
|
ssize += 1;
|
|
first = a;
|
|
limit = -3;
|
|
} else {
|
|
isad += incr;
|
|
last = a;
|
|
limit = next;
|
|
}
|
|
} else {
|
|
if 0 <= trlink {
|
|
stack[trlink as usize].3 = -1;
|
|
}
|
|
if 1 < last - a {
|
|
first = a;
|
|
limit = -3;
|
|
} else {
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
}
|
|
} else {
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if last - first <= TR_INSERTIONSORT_THRESHOLD {
|
|
tr_insertionsort(sa, isad, first, last);
|
|
limit = -3;
|
|
continue;
|
|
}
|
|
|
|
/* C decrements `limit` here (`limit-- == 0`); the decrement is
|
|
* observable only on the not-taken path because the taken path
|
|
* overwrites `limit` with -3. */
|
|
if limit == 0 {
|
|
tr_heapsort(sa, isad, first, last - first);
|
|
let mut a = last - 1;
|
|
while first < a {
|
|
let x = tr_key(sa, isad, a);
|
|
let mut b = a - 1;
|
|
while first <= b && tr_key(sa, isad, b) == x {
|
|
sa[b as usize] = !sa[b as usize];
|
|
b -= 1;
|
|
}
|
|
a = b;
|
|
}
|
|
limit = -3;
|
|
continue;
|
|
}
|
|
limit -= 1;
|
|
|
|
/* choose pivot */
|
|
let a = tr_pivot(sa, isad, first, last);
|
|
sa.swap(first as usize, a as usize);
|
|
let v = tr_key(sa, isad, first);
|
|
|
|
/* partition */
|
|
let (a, b) = tr_partition(sa, isad, first, first + 1, last, v);
|
|
if last - first != b - a {
|
|
let next = if sa[(isa + sa[a as usize] as isize) as usize] != v {
|
|
tr_ilg(b - a)
|
|
} else {
|
|
-1
|
|
};
|
|
|
|
/* update ranks */
|
|
{
|
|
let vv = (a - 1) as i32;
|
|
let mut c = first;
|
|
while c < a {
|
|
sa[(isa + sa[c as usize] as isize) as usize] = vv;
|
|
c += 1;
|
|
}
|
|
}
|
|
if b < last {
|
|
let vv = (b - 1) as i32;
|
|
let mut c = a;
|
|
while c < b {
|
|
sa[(isa + sa[c as usize] as isize) as usize] = vv;
|
|
c += 1;
|
|
}
|
|
}
|
|
|
|
/* push */
|
|
if 1 < b - a && budget.check(b - a) {
|
|
if a - first <= last - b {
|
|
if last - b <= b - a {
|
|
if 1 < a - first {
|
|
stack[ssize] = (isad + incr, a, b, next, trlink);
|
|
ssize += 1;
|
|
stack[ssize] = (isad, b, last, limit, trlink);
|
|
ssize += 1;
|
|
last = a;
|
|
} else if 1 < last - b {
|
|
stack[ssize] = (isad + incr, a, b, next, trlink);
|
|
ssize += 1;
|
|
first = b;
|
|
} else {
|
|
isad += incr;
|
|
first = a;
|
|
last = b;
|
|
limit = next;
|
|
}
|
|
} else if a - first <= b - a {
|
|
if 1 < a - first {
|
|
stack[ssize] = (isad, b, last, limit, trlink);
|
|
ssize += 1;
|
|
stack[ssize] = (isad + incr, a, b, next, trlink);
|
|
ssize += 1;
|
|
last = a;
|
|
} else {
|
|
stack[ssize] = (isad, b, last, limit, trlink);
|
|
ssize += 1;
|
|
isad += incr;
|
|
first = a;
|
|
last = b;
|
|
limit = next;
|
|
}
|
|
} else {
|
|
stack[ssize] = (isad, b, last, limit, trlink);
|
|
ssize += 1;
|
|
stack[ssize] = (isad, first, a, limit, trlink);
|
|
ssize += 1;
|
|
isad += incr;
|
|
first = a;
|
|
last = b;
|
|
limit = next;
|
|
}
|
|
} else if a - first <= b - a {
|
|
if 1 < last - b {
|
|
stack[ssize] = (isad + incr, a, b, next, trlink);
|
|
ssize += 1;
|
|
stack[ssize] = (isad, first, a, limit, trlink);
|
|
ssize += 1;
|
|
first = b;
|
|
} else if 1 < a - first {
|
|
stack[ssize] = (isad + incr, a, b, next, trlink);
|
|
ssize += 1;
|
|
last = a;
|
|
} else {
|
|
isad += incr;
|
|
first = a;
|
|
last = b;
|
|
limit = next;
|
|
}
|
|
} else if last - b <= b - a {
|
|
if 1 < last - b {
|
|
stack[ssize] = (isad, first, a, limit, trlink);
|
|
ssize += 1;
|
|
stack[ssize] = (isad + incr, a, b, next, trlink);
|
|
ssize += 1;
|
|
first = b;
|
|
} else {
|
|
stack[ssize] = (isad, first, a, limit, trlink);
|
|
ssize += 1;
|
|
isad += incr;
|
|
first = a;
|
|
last = b;
|
|
limit = next;
|
|
}
|
|
} else {
|
|
stack[ssize] = (isad, first, a, limit, trlink);
|
|
ssize += 1;
|
|
stack[ssize] = (isad, b, last, limit, trlink);
|
|
ssize += 1;
|
|
isad += incr;
|
|
first = a;
|
|
last = b;
|
|
limit = next;
|
|
}
|
|
} else {
|
|
if 1 < b - a && 0 <= trlink {
|
|
stack[trlink as usize].3 = -1;
|
|
}
|
|
if a - first <= last - b {
|
|
if 1 < a - first {
|
|
stack[ssize] = (isad, b, last, limit, trlink);
|
|
ssize += 1;
|
|
last = a;
|
|
} else if 1 < last - b {
|
|
first = b;
|
|
} else {
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
} else if 1 < last - b {
|
|
stack[ssize] = (isad, first, a, limit, trlink);
|
|
ssize += 1;
|
|
first = b;
|
|
} else if 1 < a - first {
|
|
last = a;
|
|
} else {
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
}
|
|
} else if budget.check(last - first) {
|
|
limit = tr_ilg(last - first);
|
|
isad += incr;
|
|
} else {
|
|
if 0 <= trlink {
|
|
stack[trlink as usize].3 = -1;
|
|
}
|
|
if ssize == 0 {
|
|
return;
|
|
}
|
|
ssize -= 1;
|
|
(isad, first, last, limit, trlink) = stack[ssize];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/* Tandem repeat sort */
|
|
fn trsort(sa: &mut [i32], isa: isize, n: isize, depth: isize) {
|
|
let mut budget = TrBudget::new(tr_ilg(n) * 2 / 3, n as i32);
|
|
/* trbudget_init(&budget, tr_ilg(n) * 3 / 4, n); */
|
|
let mut isad = isa + depth;
|
|
while -(n as i32) < sa[0] {
|
|
let mut first: isize = 0;
|
|
let mut skip: isize = 0;
|
|
let mut unsorted: i32 = 0;
|
|
loop {
|
|
let t0 = sa[first as usize];
|
|
if t0 < 0 {
|
|
first -= t0 as isize;
|
|
skip += t0 as isize;
|
|
} else {
|
|
if skip != 0 {
|
|
sa[(first + skip) as usize] = skip as i32;
|
|
skip = 0;
|
|
}
|
|
let last = sa[(isa + t0 as isize) as usize] as isize + 1;
|
|
if 1 < last - first {
|
|
budget.count = 0;
|
|
tr_introsort(sa, isa, isad, first, last, &mut budget);
|
|
if budget.count != 0 {
|
|
unsorted += budget.count;
|
|
} else {
|
|
skip = first - last;
|
|
}
|
|
} else if last - first == 1 {
|
|
skip = -1;
|
|
}
|
|
first = last;
|
|
}
|
|
if !(first < n) {
|
|
break;
|
|
}
|
|
}
|
|
if skip != 0 {
|
|
sa[(first + skip) as usize] = skip as i32;
|
|
}
|
|
if unsorted == 0 {
|
|
break;
|
|
}
|
|
isad += isad - isa;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/// `BUCKET_B(c0, c1)` for the 256-symbol alphabet.
|
|
#[inline(always)]
|
|
fn bb(c0: i32, c1: i32) -> usize {
|
|
(((c1 as u32) << 8) | c0 as u32) as usize
|
|
}
|
|
|
|
/// `BUCKET_BSTAR(c0, c1)` for the 256-symbol alphabet.
|
|
#[inline(always)]
|
|
fn bstar(c0: i32, c1: i32) -> usize {
|
|
(((c0 as u32) << 8) | c1 as u32) as usize
|
|
}
|
|
|
|
/* Sorts suffixes of type B*. */
|
|
fn sort_type_bstar(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
bucket_a: &mut [i32],
|
|
bucket_b: &mut [i32],
|
|
n: isize,
|
|
) -> isize {
|
|
/* Initialize bucket arrays. */
|
|
for slot in bucket_a.iter_mut() {
|
|
*slot = 0;
|
|
}
|
|
for slot in bucket_b.iter_mut() {
|
|
*slot = 0;
|
|
}
|
|
|
|
/* Count the number of occurrences of the first one or two characters of
|
|
each type A, B and B* suffix. Moreover, store the beginning position of
|
|
all type B* suffixes into the array SA. */
|
|
let mut i = n - 1;
|
|
let mut m = n;
|
|
let mut c0 = t[(n - 1) as usize] as i32;
|
|
let mut c1;
|
|
while 0 <= i {
|
|
/* type A suffix. */
|
|
loop {
|
|
c1 = c0;
|
|
bucket_a[c1 as usize] += 1;
|
|
i -= 1;
|
|
if 0 <= i {
|
|
c0 = t[i as usize] as i32;
|
|
if c0 >= c1 {
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if 0 <= i {
|
|
/* type B* suffix. */
|
|
bucket_b[bstar(c0, c1)] += 1;
|
|
m -= 1;
|
|
sa[m as usize] = i as i32;
|
|
/* type B suffix. */
|
|
i -= 1;
|
|
c1 = c0;
|
|
while 0 <= i {
|
|
c0 = t[i as usize] as i32;
|
|
if !(c0 <= c1) {
|
|
break;
|
|
}
|
|
bucket_b[bb(c0, c1)] += 1;
|
|
i -= 1;
|
|
c1 = c0;
|
|
}
|
|
}
|
|
}
|
|
let m = n - m;
|
|
/*
|
|
note:
|
|
A type B* suffix is lexicographically smaller than a type B suffix that
|
|
begins with the same first two characters.
|
|
*/
|
|
|
|
/* Calculate the index of start/end point of each bucket. */
|
|
{
|
|
let mut i: i32 = 0;
|
|
let mut j: i32 = 0;
|
|
for c0 in 0..ALPHABET_SIZE {
|
|
let t0 = i + bucket_a[c0 as usize];
|
|
bucket_a[c0 as usize] = i + j; /* start point */
|
|
i = t0 + bucket_b[bb(c0, c0)];
|
|
for c1 in (c0 + 1)..ALPHABET_SIZE {
|
|
j += bucket_b[bstar(c0, c1)];
|
|
bucket_b[bstar(c0, c1)] = j; /* end point */
|
|
i += bucket_b[bb(c0, c1)];
|
|
}
|
|
}
|
|
}
|
|
|
|
if 0 < m {
|
|
/* Sort the type B* suffixes by their first two characters. */
|
|
let pab = n - m;
|
|
let isab = m;
|
|
let mut i = m - 2;
|
|
while 0 <= i {
|
|
let t0 = sa[(pab + i) as usize];
|
|
let c0 = t[t0 as usize] as i32;
|
|
let c1 = t[(t0 + 1) as usize] as i32;
|
|
bucket_b[bstar(c0, c1)] -= 1;
|
|
sa[bucket_b[bstar(c0, c1)] as usize] = i as i32;
|
|
i -= 1;
|
|
}
|
|
{
|
|
let t0 = sa[(pab + m - 1) as usize];
|
|
let c0 = t[t0 as usize] as i32;
|
|
let c1 = t[(t0 + 1) as usize] as i32;
|
|
bucket_b[bstar(c0, c1)] -= 1;
|
|
sa[bucket_b[bstar(c0, c1)] as usize] = (m - 1) as i32;
|
|
}
|
|
|
|
/* Sort the type B* substrings using sssort. */
|
|
let buf = m;
|
|
let bufsize = n - 2 * m;
|
|
let mut c0 = ALPHABET_SIZE - 2;
|
|
let mut j = m;
|
|
while 0 < j {
|
|
let mut c1 = ALPHABET_SIZE - 1;
|
|
while c0 < c1 {
|
|
let i = bucket_b[bstar(c0, c1)] as isize;
|
|
if 1 < j - i {
|
|
sssort(
|
|
t,
|
|
sa,
|
|
pab,
|
|
i,
|
|
j,
|
|
buf,
|
|
bufsize,
|
|
2,
|
|
n,
|
|
sa[i as usize] == (m - 1) as i32,
|
|
);
|
|
}
|
|
j = i;
|
|
c1 -= 1;
|
|
}
|
|
c0 -= 1;
|
|
}
|
|
|
|
/* Compute ranks of type B* substrings. */
|
|
let mut i = m - 1;
|
|
while 0 <= i {
|
|
if 0 <= sa[i as usize] {
|
|
let j = i;
|
|
loop {
|
|
sa[(isab + sa[i as usize] as isize) as usize] = i as i32;
|
|
i -= 1;
|
|
if !(0 <= i && 0 <= sa[i as usize]) {
|
|
break;
|
|
}
|
|
}
|
|
sa[(i + 1) as usize] = (i - j) as i32;
|
|
if i <= 0 {
|
|
break;
|
|
}
|
|
}
|
|
let j = i;
|
|
loop {
|
|
sa[i as usize] = !sa[i as usize];
|
|
sa[(isab + sa[i as usize] as isize) as usize] = j as i32;
|
|
i -= 1;
|
|
if !(sa[i as usize] < 0) {
|
|
break;
|
|
}
|
|
}
|
|
sa[(isab + sa[i as usize] as isize) as usize] = j as i32;
|
|
i -= 1;
|
|
}
|
|
|
|
/* Construct the inverse suffix array of type B* suffixes using
|
|
trsort. */
|
|
trsort(sa, isab, m, 1);
|
|
|
|
/* Set the sorted order of type B* suffixes. */
|
|
let mut i = n - 1;
|
|
let mut j = m;
|
|
let mut c0 = t[(n - 1) as usize] as i32;
|
|
while 0 <= i {
|
|
i -= 1;
|
|
let mut c1 = c0;
|
|
while 0 <= i {
|
|
c0 = t[i as usize] as i32;
|
|
if !(c0 >= c1) {
|
|
break;
|
|
}
|
|
i -= 1;
|
|
c1 = c0;
|
|
}
|
|
if 0 <= i {
|
|
let t0 = i;
|
|
i -= 1;
|
|
c1 = c0;
|
|
while 0 <= i {
|
|
c0 = t[i as usize] as i32;
|
|
if !(c0 <= c1) {
|
|
break;
|
|
}
|
|
i -= 1;
|
|
c1 = c0;
|
|
}
|
|
j -= 1;
|
|
sa[sa[(isab + j) as usize] as usize] = if t0 == 0 || 1 < t0 - i {
|
|
t0 as i32
|
|
} else {
|
|
!(t0 as i32)
|
|
};
|
|
}
|
|
}
|
|
|
|
/* Calculate the index of start/end point of each bucket. */
|
|
bucket_b[bb(ALPHABET_SIZE - 1, ALPHABET_SIZE - 1)] = n as i32; /* end point */
|
|
let mut k = m - 1;
|
|
let mut c0 = ALPHABET_SIZE - 2;
|
|
while 0 <= c0 {
|
|
let mut i = bucket_a[(c0 + 1) as usize] as isize - 1;
|
|
let mut c1 = ALPHABET_SIZE - 1;
|
|
while c0 < c1 {
|
|
let t0 = i - bucket_b[bb(c0, c1)] as isize;
|
|
bucket_b[bb(c0, c1)] = i as i32; /* end point */
|
|
|
|
/* Move all type B* suffixes to the correct position. */
|
|
i = t0;
|
|
let j = bucket_b[bstar(c0, c1)] as isize;
|
|
while j <= k {
|
|
sa[i as usize] = sa[k as usize];
|
|
i -= 1;
|
|
k -= 1;
|
|
}
|
|
c1 -= 1;
|
|
}
|
|
bucket_b[bstar(c0, c0 + 1)] = (i - bucket_b[bb(c0, c0)] as isize + 1) as i32; /* start point */
|
|
bucket_b[bb(c0, c0)] = i as i32; /* end point */
|
|
c0 -= 1;
|
|
}
|
|
}
|
|
|
|
m
|
|
}
|
|
|
|
/* Constructs the suffix array by using the sorted order of type B*
|
|
* suffixes. */
|
|
fn construct_sa(
|
|
t: &[u8],
|
|
sa: &mut [i32],
|
|
bucket_a: &mut [i32],
|
|
bucket_b: &mut [i32],
|
|
n: isize,
|
|
m: isize,
|
|
) {
|
|
if 0 < m {
|
|
/* Construct the sorted order of type B suffixes by using
|
|
the sorted order of type B* suffixes. */
|
|
let mut c1 = ALPHABET_SIZE - 2;
|
|
while 0 <= c1 {
|
|
/* Scan the suffix array from right to left. */
|
|
let i = bucket_b[bstar(c1, c1 + 1)] as isize;
|
|
let mut j = bucket_a[(c1 + 1) as usize] as isize - 1;
|
|
let mut k: isize = 0;
|
|
let mut c2: i32 = -1;
|
|
while i <= j {
|
|
let mut s = sa[j as usize];
|
|
if 0 < s {
|
|
debug_assert_eq!(t[s as usize] as i32, c1);
|
|
debug_assert!((s as isize + 1) < n && t[s as usize] <= t[(s + 1) as usize]);
|
|
debug_assert!(t[(s - 1) as usize] <= t[s as usize]);
|
|
sa[j as usize] = !s;
|
|
s -= 1;
|
|
let c0 = t[s as usize] as i32;
|
|
if 0 < s && (t[(s - 1) as usize] as i32) > c0 {
|
|
s = !s;
|
|
}
|
|
if c0 != c2 {
|
|
if 0 <= c2 {
|
|
bucket_b[bb(c2, c1)] = k as i32;
|
|
}
|
|
c2 = c0;
|
|
k = bucket_b[bb(c2, c1)] as isize;
|
|
}
|
|
debug_assert!(k < j);
|
|
sa[k as usize] = s;
|
|
k -= 1;
|
|
} else {
|
|
debug_assert!((s == 0 && t[s as usize] as i32 == c1) || s < 0);
|
|
sa[j as usize] = !s;
|
|
}
|
|
j -= 1;
|
|
}
|
|
c1 -= 1;
|
|
}
|
|
}
|
|
|
|
/* Construct the suffix array by using the sorted order of type B
|
|
suffixes. */
|
|
let mut c2 = t[(n - 1) as usize] as i32;
|
|
let mut k = bucket_a[c2 as usize] as isize;
|
|
sa[k as usize] = if (t[(n - 2) as usize] as i32) < c2 {
|
|
!((n - 1) as i32)
|
|
} else {
|
|
(n - 1) as i32
|
|
};
|
|
k += 1;
|
|
/* Scan the suffix array from left to right. */
|
|
let mut i: isize = 0;
|
|
let j = n;
|
|
while i < j {
|
|
let mut s = sa[i as usize];
|
|
if 0 < s {
|
|
debug_assert!(t[(s - 1) as usize] >= t[s as usize]);
|
|
s -= 1;
|
|
let c0 = t[s as usize] as i32;
|
|
if s == 0 || (t[(s - 1) as usize] as i32) < c0 {
|
|
s = !s;
|
|
}
|
|
if c0 != c2 {
|
|
bucket_a[c2 as usize] = k as i32;
|
|
c2 = c0;
|
|
k = bucket_a[c2 as usize] as isize;
|
|
}
|
|
debug_assert!(i < k);
|
|
sa[k as usize] = s;
|
|
k += 1;
|
|
} else {
|
|
debug_assert!(s < 0);
|
|
sa[i as usize] = !s;
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
/// Rust implementation of the `divsufsort()` entry point used by
|
|
/// `ZDICT_trainFromBuffer_legacy()`.
|
|
///
|
|
/// Integration removes the C function body, so this direct export provides
|
|
/// the existing library symbol without a wrapper. The `open_mp` parameter is
|
|
/// accepted for signature compatibility only: zstd never defines
|
|
/// `LIBBSC_OPENMP`, so the C implementation ignored it as well.
|
|
///
|
|
/// Returns 0 on success, -1 for invalid arguments, and -2 when the bucket
|
|
/// work arrays cannot be allocated, exactly like the C routine.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn divsufsort(
|
|
t: *const u8,
|
|
sa: *mut c_int,
|
|
n: c_int,
|
|
open_mp: c_int,
|
|
) -> c_int {
|
|
let _ = open_mp;
|
|
|
|
/* Check arguments. */
|
|
if t.is_null() || sa.is_null() || n < 0 {
|
|
return -1;
|
|
}
|
|
if n == 0 {
|
|
return 0;
|
|
}
|
|
|
|
let text = unsafe { slice::from_raw_parts(t, n as usize) };
|
|
let suffix = unsafe { slice::from_raw_parts_mut(sa, n as usize) };
|
|
if n == 1 {
|
|
suffix[0] = 0;
|
|
return 0;
|
|
}
|
|
if n == 2 {
|
|
let m = usize::from(text[0] < text[1]);
|
|
suffix[m ^ 1] = 0;
|
|
suffix[m] = 1;
|
|
return 0;
|
|
}
|
|
|
|
let mut bucket_a: Vec<i32> = Vec::new();
|
|
let mut bucket_b: Vec<i32> = Vec::new();
|
|
if bucket_a.try_reserve_exact(BUCKET_A_SIZE).is_err()
|
|
|| bucket_b.try_reserve_exact(BUCKET_B_SIZE).is_err()
|
|
{
|
|
/* Match the C implementation's -2 result when malloc fails. */
|
|
return -2;
|
|
}
|
|
bucket_a.resize(BUCKET_A_SIZE, 0);
|
|
bucket_b.resize(BUCKET_B_SIZE, 0);
|
|
|
|
/* Suffixsort. */
|
|
let m = sort_type_bstar(text, suffix, &mut bucket_a, &mut bucket_b, n as isize);
|
|
construct_sa(text, suffix, &mut bucket_a, &mut bucket_b, n as isize, m);
|
|
0
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::ptr;
|
|
|
|
fn build_sa(text: &[u8]) -> Vec<i32> {
|
|
let mut sa = vec![0i32; text.len()];
|
|
let result = unsafe { divsufsort(text.as_ptr(), sa.as_mut_ptr(), text.len() as c_int, 0) };
|
|
assert_eq!(result, 0);
|
|
sa
|
|
}
|
|
|
|
/// Trivial O(n^2 log n) reference: sort the suffix start positions by the
|
|
/// suffixes themselves.
|
|
fn reference_sa(text: &[u8]) -> Vec<i32> {
|
|
let mut sa: Vec<i32> = (0..text.len() as i32).collect();
|
|
sa.sort_by(|&a, &b| text[a as usize..].cmp(&text[b as usize..]));
|
|
sa
|
|
}
|
|
|
|
/// Suffix-array invariants: a permutation of `0..n` whose suffixes are in
|
|
/// strictly increasing lexicographic order.
|
|
fn assert_valid_sa(text: &[u8], sa: &[i32]) {
|
|
assert_eq!(sa.len(), text.len());
|
|
let mut seen = vec![false; text.len()];
|
|
for &p in sa {
|
|
let p = usize::try_from(p).expect("suffix index must be non-negative");
|
|
assert!(p < text.len(), "suffix index {p} out of range");
|
|
assert!(!seen[p], "duplicate suffix index {p}");
|
|
seen[p] = true;
|
|
}
|
|
for pair in sa.windows(2) {
|
|
assert!(
|
|
text[pair[0] as usize..] < text[pair[1] as usize..],
|
|
"suffixes {} and {} are not in sorted order",
|
|
pair[0],
|
|
pair[1]
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Fixed-seed numerical-recipes LCG, used to generate reproducible
|
|
/// pseudo-random sample buffers.
|
|
fn lcg_bytes(len: usize, seed: u32, alphabet: u32) -> Vec<u8> {
|
|
let mut state = seed;
|
|
(0..len)
|
|
.map(|_| {
|
|
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
|
|
((state >> 24) % alphabet) as u8
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_invalid_arguments() {
|
|
let text = [0u8; 1];
|
|
let mut sa = [0i32; 1];
|
|
assert_eq!(
|
|
unsafe { divsufsort(ptr::null(), sa.as_mut_ptr(), 1, 0) },
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
unsafe { divsufsort(text.as_ptr(), ptr::null_mut(), 1, 0) },
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
unsafe { divsufsort(text.as_ptr(), sa.as_mut_ptr(), -1, 0) },
|
|
-1
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sorts_trivial_inputs() {
|
|
/* empty */
|
|
let text = [0u8; 1];
|
|
let mut sa = [i32::MIN; 1];
|
|
assert_eq!(
|
|
unsafe { divsufsort(text.as_ptr(), sa.as_mut_ptr(), 0, 0) },
|
|
0
|
|
);
|
|
assert_eq!(sa[0], i32::MIN, "n == 0 must not touch the output");
|
|
|
|
/* single byte */
|
|
assert_eq!(build_sa(b"z"), [0]);
|
|
|
|
/* two bytes: ascending, descending, and equal */
|
|
assert_eq!(build_sa(b"ab"), [0, 1]);
|
|
assert_eq!(build_sa(b"ba"), [1, 0]);
|
|
assert_eq!(build_sa(b"aa"), [1, 0]);
|
|
}
|
|
|
|
#[test]
|
|
fn sorts_all_equal_bytes() {
|
|
let text = vec![b'q'; 10_000];
|
|
let sa = build_sa(&text);
|
|
/* For a constant text the shortest suffix sorts first. */
|
|
let expected: Vec<i32> = (0..text.len() as i32).rev().collect();
|
|
assert_eq!(sa, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn sorts_abracadabra_exactly() {
|
|
/* Hand-computed: a(10) abra(7) abracadabra(0) acadabra(3) adabra(5)
|
|
* bra(8) bracadabra(1) cadabra(4) dabra(6) ra(9) racadabra(2). */
|
|
assert_eq!(build_sa(b"abracadabra"), [10, 7, 0, 3, 5, 8, 1, 4, 6, 9, 2]);
|
|
}
|
|
|
|
#[test]
|
|
fn matches_reference_on_periodic_text() {
|
|
/* Tandem repeats exercise trsort's repeat partitioning. */
|
|
let text: Vec<u8> = b"ab".iter().copied().cycle().take(4096).collect();
|
|
let sa = build_sa(&text);
|
|
assert_valid_sa(&text, &sa);
|
|
assert_eq!(sa, reference_sa(&text));
|
|
}
|
|
|
|
#[test]
|
|
fn matches_reference_on_random_bytes() {
|
|
let text = lcg_bytes(8192, 0x0BAD_5EED, 256);
|
|
let sa = build_sa(&text);
|
|
assert_valid_sa(&text, &sa);
|
|
assert_eq!(sa, reference_sa(&text));
|
|
}
|
|
|
|
#[test]
|
|
fn matches_reference_on_low_alphabet_text() {
|
|
/* A four-symbol alphabet produces the large first-two-character
|
|
* buckets that reach sssort's block merging and the deeper trsort
|
|
* paths. */
|
|
let text = lcg_bytes(16_384, 0xDEAD_BEEF, 4);
|
|
let sa = build_sa(&text);
|
|
assert_valid_sa(&text, &sa);
|
|
assert_eq!(sa, reference_sa(&text));
|
|
}
|
|
}
|