use std::time::Instant;
const RUN_CHUNK: usize = 16;
pub(crate) fn common_prefix<T: Eq>(a: &[T], b: &[T]) -> usize {
let n = a.len().min(b.len());
let scalar_end = n.min(RUN_CHUNK);
let mut i = 0;
while i < scalar_end && a[i] == b[i] {
i += 1;
}
if i == RUN_CHUNK {
while i + RUN_CHUNK <= n && a[i..i + RUN_CHUNK] == b[i..i + RUN_CHUNK] {
i += RUN_CHUNK;
}
while i < n && a[i] == b[i] {
i += 1;
}
}
i
}
pub(crate) fn common_suffix<T: Eq>(a: &[T], b: &[T]) -> usize {
let n = a.len().min(b.len());
let scalar_end = n.min(RUN_CHUNK);
let mut i = 0;
while i < scalar_end && a[a.len() - 1 - i] == b[b.len() - 1 - i] {
i += 1;
}
if i == RUN_CHUNK {
while i + RUN_CHUNK <= n
&& a[a.len() - i - RUN_CHUNK..a.len() - i] == b[b.len() - i - RUN_CHUNK..b.len() - i]
{
i += RUN_CHUNK;
}
while i < n && a[a.len() - 1 - i] == b[b.len() - 1 - i] {
i += 1;
}
}
i
}
pub(crate) fn common_overlap<T: Eq>(a: &[T], b: &[T]) -> usize {
if a.is_empty() || b.is_empty() {
return 0;
}
let len = a.len().min(b.len());
let a = &a[a.len() - len..];
let b = &b[..len];
if a == b {
return len;
}
let mut kmp: Option<Kmp<T>> = None;
let mut state = 0;
let mut i = 0;
while i < a.len() {
if a[i] == b[state] {
state += 1;
i += 1;
} else if state == 0 {
match skip_to(a, i + 1, &b[0]) {
Some(j) => i = j,
None => return 0,
}
} else {
state = kmp.get_or_insert_with(|| Kmp::new(b)).fail(state);
}
}
state
}
struct Kmp<'a, T> {
needle: &'a [T],
table: Vec<usize>,
len: usize,
next: usize,
}
impl<'a, T: Eq> Kmp<'a, T> {
fn new(needle: &'a [T]) -> Self {
Kmp {
needle,
table: vec![0],
len: 0,
next: 1,
}
}
fn fail(&mut self, state: usize) -> usize {
let k = state - 1;
while self.table.len() <= k {
let i = self.next;
while self.len > 0 && self.needle[i] != self.needle[self.len] {
self.len = self.table[self.len - 1];
}
if self.needle[i] == self.needle[self.len] {
self.len += 1;
}
self.table.push(self.len);
self.next += 1;
}
self.table[k]
}
}
pub(crate) fn find_sub<T: Eq>(hay: &[T], needle: &[T], from: usize) -> Option<usize> {
if needle.is_empty() {
return Some(from);
}
if hay.is_empty() {
return None;
}
let mut kmp: Option<Kmp<T>> = None;
let mut i = from;
let mut len = 0;
while i < hay.len() {
if hay[i] == needle[len] {
len += 1;
i += 1;
if len == needle.len() {
return Some(i - len);
}
} else if len == 0 {
match skip_to(hay, i + 1, &needle[0]) {
Some(j) => i = j,
None => return None,
}
} else {
len = kmp.get_or_insert_with(|| Kmp::new(needle)).fail(len);
}
}
None
}
pub(crate) fn contains<T: Eq>(hay: &[T], needle: &[T]) -> Option<usize> {
if needle.is_empty() {
return Some(0);
}
if needle.len() > hay.len() {
return None;
}
let last_start = hay.len() - needle.len();
let mut budget: i64 = (hay.len() + needle.len()) as i64;
let mut i = 0;
while i <= last_start {
let j = match skip_to(hay, i, &needle[0]) {
Some(j) if j <= last_start => j,
_ => return None,
};
let matched = common_prefix(&hay[j..], needle);
if matched == needle.len() {
return Some(j);
}
budget -= matched as i64 + 1;
if budget < 0 {
return find_sub(hay, needle, 0);
}
i = j + 1;
}
None
}
pub(crate) fn rfind_sub<T: Eq>(hay: &[T], needle: &[T], until: usize) -> Option<usize> {
if needle.is_empty() {
return Some(until);
}
if hay.is_empty() {
return None;
}
let mut kmp: Option<Kmp<T>> = None;
let mut i = 0;
let mut len = 0;
let mut last: Option<usize> = None;
while i <= until {
if i < hay.len() && hay[i] == needle[len] {
len += 1;
i += 1;
if len == needle.len() {
last = Some(i - len);
len = kmp.get_or_insert_with(|| Kmp::new(needle)).fail(len);
}
} else if len == 0 {
match skip_to(hay, i + 1, &needle[0]) {
Some(j) => i = j,
None => return last,
}
} else {
len = kmp.get_or_insert_with(|| Kmp::new(needle)).fail(len);
}
}
last
}
pub(crate) fn skip_to<T: Eq>(hay: &[T], from: usize, target: &T) -> Option<usize> {
const SKIP_CHUNK: usize = 16;
let mut i = from;
while i + SKIP_CHUNK <= hay.len() {
let mut any = false;
for token in &hay[i..i + SKIP_CHUNK] {
any |= token == target;
}
if any {
break;
}
i += SKIP_CHUNK;
}
while i < hay.len() {
if hay[i] == *target {
return Some(i);
}
i += 1;
}
None
}
fn skip_rank(c: char) -> u8 {
match c {
' ' => 255,
'e' | 't' | 'a' | 'o' | 'i' | 'n' | 's' | 'r' | 'h' | 'l' => 250,
'd' | 'c' | 'u' | 'm' | 'f' | 'g' | 'p' | 'w' | 'y' | 'b' | '\n' => 240,
'v' | 'k' | '.' | ',' | '-' | '\'' | '"' | ';' | ':' | '=' | '/' | '<' | '>' => 220,
'0'..='9' => 200,
'A'..='Z' => 190,
c if c.is_ascii_graphic() => 150,
c if c.is_ascii() => 120,
_ => 100,
}
}
fn rarest_index(needle: &[char]) -> usize {
let mut best = 0;
let mut best_rank = u8::MAX;
for (i, &c) in needle.iter().enumerate() {
let rank = skip_rank(c);
if rank < best_rank {
best_rank = rank;
best = i;
}
}
best
}
pub(crate) fn occurs_twice(hay: &[char], needle: &[char]) -> bool {
if needle.is_empty() || hay.len() < needle.len() {
return false;
}
let k = rarest_index(needle);
let target = &needle[k];
let last_start = hay.len() - needle.len();
let mut found = false;
let mut p = k;
while let Some(hit) = skip_to(hay, p, target) {
let j = hit - k;
if j > last_start {
break;
}
if hay[j..j + needle.len()] == *needle {
if found {
return true;
}
found = true;
}
p = hit + 1;
}
false
}
pub(crate) struct HalfMatch {
pub old_a: usize,
pub new_a: usize,
pub common: usize,
}
pub(crate) fn half_match<T: Eq>(old: &[T], new: &[T]) -> Option<HalfMatch> {
let old_is_long = old.len() > new.len();
let (long, short) = if old_is_long { (old, new) } else { (new, old) };
if long.len() < 4 || short.len() * 2 < long.len() {
return None;
}
let hm1 = half_match_at(long, short, long.len().div_ceil(4));
let hm2 = half_match_at(long, short, long.len().div_ceil(2));
let hm = match (hm1, hm2) {
(None, None) => return None,
(Some(h), None) => h,
(None, Some(h)) => h,
(Some(h1), Some(h2)) => {
if h1.2 > h2.2 {
h1
} else {
h2
}
}
};
let (long_a, short_a, common) = hm;
Some(if old_is_long {
HalfMatch {
old_a: long_a,
new_a: short_a,
common,
}
} else {
HalfMatch {
old_a: short_a,
new_a: long_a,
common,
}
})
}
fn half_match_at<T: Eq>(long: &[T], short: &[T], i: usize) -> Option<(usize, usize, usize)> {
let seed = &long[i..i + long.len() / 4];
let mut best_common = 0;
let mut best = (0, 0);
let mut kmp = Kmp::new(seed);
let mut pos = 0;
let mut len = 0;
while pos < short.len() {
if short[pos] == seed[len] {
pos += 1;
len += 1;
if len == seed.len() {
let jv = pos - len;
let cap = (long.len() - i).min(short.len() - jv) + i.min(jv);
if cap > best_common {
let prefix = common_prefix(&long[i..], &short[jv..]);
let suffix = common_suffix(&long[..i], &short[..jv]);
if best_common < suffix + prefix {
best_common = suffix + prefix;
best = (i - suffix, jv - suffix);
}
}
len = kmp.fail(len);
}
} else if len == 0 {
pos += 1;
} else {
len = kmp.fail(len);
}
}
if best_common * 2 >= long.len() {
Some((best.0, best.1, best_common))
} else {
None
}
}
#[inline]
fn snake_fwd<T: Eq>(old: &[T], new: &[T], mut x: usize, mut y: usize) -> (usize, usize) {
while x < old.len() && y < new.len() && old[x] == new[y] {
x += 1;
y += 1;
}
(x, y)
}
#[inline]
fn snake_rev<T: Eq>(old: &[T], new: &[T], mut x: usize, mut y: usize) -> (usize, usize) {
while x < old.len() && y < new.len() && old[old.len() - 1 - x] == new[new.len() - 1 - y] {
x += 1;
y += 1;
}
(x, y)
}
pub(crate) fn bisect<T: Eq>(
old: &[T],
new: &[T],
deadline: Option<Instant>,
scratch: &mut Vec<i32>,
) -> Option<(usize, usize)> {
let text1_length = old.len() as i32;
let text2_length = new.len() as i32;
let max_d: i32 = (text1_length + text2_length + 1) / 2;
let v_offset: i32 = max_d;
let v_length: i32 = 2 * max_d;
scratch.clear();
scratch.resize(2 * v_length as usize, -1);
let (v1, v2) = scratch.split_at_mut(v_length as usize);
v1[v_offset as usize + 1] = 0;
v2[v_offset as usize + 1] = 0;
let delta: i32 = text1_length - text2_length;
let front: bool = delta % 2 != 0;
let mut k1start: i32 = 0;
let mut k1end: i32 = 0;
let mut k2start: i32 = 0;
let mut k2end: i32 = 0;
for d in 0..max_d {
if let Some(deadline) = deadline {
if Instant::now() >= deadline {
break;
}
}
let mut k1 = -d + k1start;
while k1 < d + 1 - k1end {
let k1_offset = (v_offset + k1) as usize;
debug_assert!(k1_offset >= 1 && k1_offset < v_length as usize);
let v1_prev = v1.get(k1_offset - 1).copied().unwrap_or(-1);
let v1_next = v1.get(k1_offset + 1).copied().unwrap_or(-1);
let x_start = if k1 == -d || (k1 != d && v1_prev < v1_next) {
v1_next
} else {
v1_prev + 1
};
let y_start = x_start - k1;
debug_assert!(
x_start >= 0 && y_start >= 0,
"fwd walk start ({x_start},{y_start})"
);
let (x1, y1) = snake_fwd(old, new, x_start as usize, y_start as usize);
if let Some(cell) = v1.get_mut(k1_offset) {
*cell = x1 as i32;
}
if x1 > old.len() {
k1end += 2;
} else if y1 > new.len() {
k1start += 2;
} else if front {
let k2_offset = v_offset + delta - k1;
if k2_offset >= 0 {
if let Some(&v2k) = v2.get(k2_offset as usize) {
if v2k != -1 {
let x2 = text1_length - v2k;
if x1 as i32 >= x2 {
return Some((x1, y1));
}
}
}
}
}
k1 += 2;
}
let mut k2 = -d + k2start;
while k2 < d + 1 - k2end {
let k2_offset = (v_offset + k2) as usize;
debug_assert!(k2_offset >= 1 && k2_offset < v_length as usize);
let v2_prev = v2.get(k2_offset - 1).copied().unwrap_or(-1);
let v2_next = v2.get(k2_offset + 1).copied().unwrap_or(-1);
let x_start = if k2 == -d || (k2 != d && v2_prev < v2_next) {
v2_next
} else {
v2_prev + 1
};
let y_start = x_start - k2;
debug_assert!(
x_start >= 0 && y_start >= 0,
"rev walk start ({x_start},{y_start})"
);
let (x2, y2) = snake_rev(old, new, x_start as usize, y_start as usize);
if let Some(cell) = v2.get_mut(k2_offset) {
*cell = x2 as i32;
}
if x2 > old.len() {
k2end += 2;
} else if y2 > new.len() {
k2start += 2;
} else if !front {
let k1_offset = v_offset + delta - k2;
if k1_offset >= 0 {
if let Some(&v1k) = v1.get(k1_offset as usize) {
if v1k != -1 {
let y1 = v_offset + v1k - k1_offset;
if v1k >= text1_length - x2 as i32 {
return Some((v1k as usize, y1 as usize));
}
}
}
}
}
k2 += 2;
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn common_prefix_suffix_over_bytes() {
assert_eq!(common_prefix(b"1234abcdef", b"1234xyz"), 4);
assert_eq!(common_prefix(b"abc", b"xyz"), 0);
assert_eq!(common_prefix(b"1234", b"1234xyz"), 4);
assert_eq!(common_suffix(b"abcdef1234", b"xyz1234"), 4);
assert_eq!(common_suffix(b"abc", b"xyz"), 0);
assert_eq!(common_suffix(b"1234", b"xyz1234"), 4);
let empty: &[u8] = b"";
assert_eq!(common_prefix(empty, b"a"), 0);
assert_eq!(common_suffix(empty, b"a"), 0);
}
#[test]
fn common_overlap_over_bytes() {
let empty: &[u8] = b"";
assert_eq!(common_overlap(empty, b"abcd"), 0);
assert_eq!(common_overlap(b"abc", b"abcd"), 3);
assert_eq!(common_overlap(b"123456", b"abcd"), 0);
assert_eq!(common_overlap(b"123456xxx", b"xxxabcd"), 3);
assert_eq!(common_overlap(b"fi", b"\x01ab"), 0);
}
#[test]
fn find_sub_over_bytes() {
assert_eq!(find_sub(b"abcdefabcdef", b"cde", 0), Some(2));
assert_eq!(find_sub(b"abcdefabcdef", b"cde", 3), Some(8));
assert_eq!(find_sub(b"abcdef", b"xyz", 0), None);
assert_eq!(find_sub(b"abc", b"", 1), Some(1));
assert_eq!(find_sub(b"", b"a", 0), None);
assert_eq!(find_sub(b"aaa", b"aaaa", 0), None);
}
#[test]
fn rfind_sub_over_bytes() {
assert_eq!(rfind_sub(b"abcabcabc", b"abc", 8), Some(6));
assert_eq!(rfind_sub(b"abcabcabc", b"abc", 5), Some(3));
assert_eq!(rfind_sub(b"xxabc", b"abc", 4), Some(2));
assert_eq!(rfind_sub(b"xxabc", b"abc", 3), None);
assert_eq!(rfind_sub(b"ab", b"b", 2), Some(1));
assert_eq!(rfind_sub(b"ab", b"b", 9), Some(1));
assert_eq!(rfind_sub(b"abc", b"", 2), Some(2));
assert_eq!(rfind_sub(b"", b"a", 0), None);
}
#[test]
fn occurs_twice_over_chars() {
let c = |s: &str| s.chars().collect::<Vec<char>>();
let ot = |hay: &str, needle: &str| occurs_twice(&c(hay), &c(needle));
assert!(!ot("abcdef", "cde")); assert!(ot("abcdefabc", "abc")); assert!(ot("aaaa", "aaa")); assert!(!ot("abc", "xyz")); assert!(!ot("", "a"));
assert!(!ot("ab", "abc")); assert!(ot("abxxxxxxxxxxxxxxxxxxab", "ab"));
assert!(!ot("axxxxxxxxxxxxxxxxxxxab", "ab"));
assert!(ot("eee eQe eee eQe", "eQe"));
assert!(!ot("eee eQe eee eee", "eQe"));
assert!(ot("aQaQa", "aQa")); assert!(!ot("ab", ""));
}
#[test]
fn half_match_over_bytes() {
assert!(half_match(b"1234567890".as_slice(), b"abcdef".as_slice()).is_none());
assert!(half_match(b"12345".as_slice(), b"23".as_slice()).is_none());
let hm = half_match(b"1234567890".as_slice(), b"a345678z".as_slice()).unwrap();
assert_eq!((hm.old_a, hm.new_a, hm.common), (2, 1, 6));
let hm = half_match(b"a345678z".as_slice(), b"1234567890".as_slice()).unwrap();
assert_eq!((hm.old_a, hm.new_a, hm.common), (1, 2, 6));
}
#[test]
fn bisect_over_bytes() {
let mut scratch = Vec::new();
assert_eq!(bisect(b"cat", b"map", None, &mut scratch), Some((2, 2)));
assert_eq!(bisect(b"abc", b"xyz", None, &mut scratch), None);
let past = Instant::now() - std::time::Duration::from_secs(1);
assert_eq!(bisect(b"cat", b"map", Some(past), &mut scratch), None);
}
#[test]
fn snake_walks() {
assert_eq!(snake_fwd(b"abcX", b"abcY", 0, 0), (3, 3));
assert_eq!(snake_fwd(b"abc", b"abc", 1, 1), (3, 3));
assert_eq!(snake_fwd(b"xxab", b"abyy", 2, 0), (4, 2));
assert_eq!(snake_fwd(b"ab", b"ab", 2, 2), (2, 2));
assert_eq!(snake_fwd(b"ab", b"ab", 3, 3), (3, 3));
assert_eq!(snake_rev(b"Xabc", b"Yabc", 0, 0), (3, 3));
assert_eq!(snake_rev(b"abcd", b"cd", 0, 0), (2, 2));
assert_eq!(snake_rev(b"ab", b"ab", 2, 2), (2, 2));
}
#[test]
fn contains_matches_find_sub() {
let cases: &[(&[u8], &[u8])] = &[
(b"", b""),
(b"abc", b""),
(b"abcdef", b"cd"), (b"abcdef", b"abc"), (b"abcdef", b"def"), (b"abcdef", b"xyz"), (b"abcdef", b"abcdefg"), (b"XabcY", b"abc"), (b"aaaab", b"aab"), (b"abababab", b"ababab"), (b"abababab", b"babab"), (b"aaaaaaaa", b"aaaaaaa"), (b"aaaaaaab", b"aaaab"), ];
for &(hay, needle) in cases {
assert_eq!(
contains(hay, needle),
find_sub(hay, needle, 0),
"contains disagrees with find_sub on hay={hay:?} needle={needle:?}"
);
}
}
}