O(n ** 2) -> O(n)
This commit is contained in:
parent
ad296b4853
commit
10e2f1a3b9
1 changed files with 74 additions and 6 deletions
|
|
@ -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<T>(old: &[Token<T>], new: &[Token<T>]) -> Range<usize>
|
||||
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<String>], new: &[Token<String>]) -> Range<usize> {
|
||||
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<Token<String>>> = 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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue