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]
|
/// new: [0, 4]
|
||||||
/// ```
|
/// ```
|
||||||
/// > results in a range of 0..1
|
/// > 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>
|
pub fn find_longest_prefix_contained_within<T>(old: &[Token<T>], new: &[Token<T>]) -> Range<usize>
|
||||||
where
|
where
|
||||||
T: PartialEq + Clone + Debug,
|
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() {
|
// `fallback[i]` is the length of the longest proper prefix of `new[..=i]`
|
||||||
let prefix = &new[..len];
|
// that is also a suffix of it
|
||||||
if let Some(start) = old.windows(len).position(|window| window == prefix) {
|
let mut fallback = vec![0; new.len()];
|
||||||
return start..start + 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)]
|
#[cfg(test)]
|
||||||
|
|
@ -111,4 +141,42 @@ mod tests {
|
||||||
0..0
|
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