diff --git a/src/utils/find_longest_prefix_contained_within.rs b/src/utils/find_longest_prefix_contained_within.rs index feff917..c167d3e 100644 --- a/src/utils/find_longest_prefix_contained_within.rs +++ b/src/utils/find_longest_prefix_contained_within.rs @@ -26,20 +26,50 @@ use crate::Token; /// new: [0, 4] /// ``` /// > results in a range of 0..1 +/// +/// Runs in `O(old.len() + new.len())` using the Knuth-Morris-Pratt algorithm. pub fn find_longest_prefix_contained_within(old: &[Token], new: &[Token]) -> Range where T: PartialEq + Clone + Debug, { - let max_possible = new.len().min(old.len()); + if new.is_empty() { + return 0..0; + } - for len in (1..=max_possible).rev() { - let prefix = &new[..len]; - if let Some(start) = old.windows(len).position(|window| window == prefix) { - return start..start + len; + // `fallback[i]` is the length of the longest proper prefix of `new[..=i]` + // that is also a suffix of it + let mut fallback = vec![0; new.len()]; + let mut matched = 0; + for i in 1..new.len() { + while matched > 0 && new[i] != new[matched] { + matched = fallback[matched - 1]; + } + if new[i] == new[matched] { + matched += 1; + } + fallback[i] = matched; + } + + // `matched` is the longest prefix of `new` ending at the current token of + // `old`, so it first reaches its maximum at the earliest occurrence + let mut longest = 0..0; + matched = 0; + for (i, token) in old.iter().enumerate() { + while matched > 0 && *token != new[matched] { + matched = fallback[matched - 1]; + } + if *token == new[matched] { + matched += 1; + } + if matched > longest.len() { + longest = i + 1 - matched..i + 1; + if matched == new.len() { + break; + } } } - 0..0 + longest } #[cfg(test)] @@ -111,4 +141,42 @@ mod tests { 0..0 ); } + + #[test] + fn test_matches_brute_force_on_short_sequences() { + fn brute_force(old: &[Token], new: &[Token]) -> Range { + for len in (1..=new.len().min(old.len())).rev() { + if let Some(start) = old.windows(len).position(|window| window == &new[..len]) { + return start..start + len; + } + } + 0..0 + } + + let mut sequences: Vec>> = vec![Vec::new()]; + let mut level = sequences.clone(); + for _ in 0..5 { + level = level + .iter() + .flat_map(|sequence| { + ["a", "b", "c"].map(|token| { + let mut extended = sequence.clone(); + extended.push(token.into()); + extended + }) + }) + .collect(); + sequences.extend(level.iter().cloned()); + } + + for old in &sequences { + for new in &sequences { + assert_eq!( + find_longest_prefix_contained_within(old, new), + brute_force(old, new), + "old={old:?}, new={new:?}" + ); + } + } + } }