use alloc::vec::Vec;
use crate::{kmp_find_with_lsp_table, kmp_table};
pub fn kmp_match<N, H>(needle: &[N], haystack: &[H]) -> Vec<usize>
where
N: PartialEq,
H: PartialEq<N>,
{
kmp_match_with_lsp_table(needle, haystack, &kmp_table(needle))
}
pub fn kmp_match_with_lsp_table<N, H>(needle: &[N], haystack: &[H], lsp: &[usize]) -> Vec<usize>
where
N: PartialEq,
H: PartialEq<N>,
{
if needle.is_empty() {
return (0..=haystack.len()).collect();
}
let mut start = 0;
let mut locations = Vec::new();
#[allow(clippy::indexing_slicing, clippy::integer_arithmetic)]
while let Some(position) = kmp_find_with_lsp_table(needle, &haystack[start..], lsp) {
let position = position + start;
start = position + needle.len();
locations.push(position);
if start > haystack.len() - (needle.len() - 1) {
break;
}
}
locations
}
#[cfg(test)]
mod tests {
use alloc::vec;
use proptest::prelude::*;
use crate::kmp_match;
#[test]
fn basic() {
assert_eq!(
vec![0, 6, 12],
kmp_match(
&['a', 'b', 'c'],
&['a', 'b', 'c', 'X', 'X', 'X', 'a', 'b', 'c', 'Y', 'Y', 'Y', 'a', 'b', 'c'],
)
);
}
#[test]
fn concatenated() {
assert_eq!(
vec![1, 4],
kmp_match(&['a', 'b', 'c'], &['1', 'a', 'b', 'c', 'a', 'b', 'c', '2'])
);
}
#[test]
fn combined() {
assert_eq!(
vec![1],
kmp_match(&['a', 'b', 'a'], &['1', 'a', 'b', 'a', 'b', 'a', '2'])
);
}
#[test]
fn empty_needle() {
assert_eq!(vec![0, 1, 2], kmp_match(&[], &['a', 'b']));
}
#[test]
fn empty_haystack() {
let empty_haystack: &[char; 0] = &[];
assert!(kmp_match(&['a', 'b', 'c'], empty_haystack).is_empty());
}
#[test]
fn empty_both() {
let empty_needle: &[char; 0] = &[];
let empty_haystack: &[char; 0] = &[];
assert_eq!(vec![0], kmp_match(empty_needle, empty_haystack));
}
#[test]
fn needle_longer_haystack() {
assert!(kmp_match(&['a', 'b', 'c'], &['a', 'b']).is_empty());
}
proptest! {
#[ignore]
#[test]
fn fuzz_input(needle in prop::collection::vec(".*", 0..100), haystack in prop::collection::vec(".*", 0..100)) {
kmp_match(&needle, &haystack);
}
}
}