1pub mod minspan {
4
5 pub fn span<A>(query: &Vec<A>, history: &Vec<A>) -> Option<(usize, usize)>
6 where
7 A: PartialEq,
8 {
9 if history.is_empty() {
11 return None;
12 }
13 if query.is_empty() {
17 return Some((0, 0));
18 }
19
20 let mut starting_at: Vec<Option<(usize, usize)>> = query.iter().map(|_| None).collect();
22 let mut best_complete_solution: Option<(usize, usize)> = None;
23
24 for (bodyindex, bodychr) in history.iter().enumerate() {
26 for (keyindex, keychr) in query.iter().enumerate().rev() {
27 if keychr == bodychr {
28 starting_at[keyindex] = if keyindex == 0 {
31 Some((bodyindex, bodyindex))
33 } else {
34 starting_at[keyindex - 1].map(|(start, _end)| (start, bodyindex))
35 };
36 if (keyindex + 1) == query.len() {
38 if let Some((from, to)) = starting_at[keyindex] {
39 best_complete_solution = match best_complete_solution {
40 None => Some((from, to)), Some((currfrom, currto)) => {
42 Some(if to - from < currto - currfrom {
43 (from, to)
44 } else {
45 (currfrom, currto)
46 })
47 }
48 }
49 }
50 }
51 }
52 }
53 }
54 best_complete_solution
55 }
56}
57
58#[cfg(test)]
60extern crate proptest;
61#[cfg(test)]
62use proptest::prelude::*;
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 #[test]
69 fn test_minimal_match() {
70 let run_span = |needle: &str, haystack: &str| {
72 let query: Vec<char> = needle.chars().collect();
73 let history: Vec<char> = haystack.chars().collect();
74 minspan::span(&query, &history)
75 };
76
77 let get_span_len = |needle: &str, haystack: &str| {
79 run_span(needle, haystack).map(|(from, to)| 1 + to - from)
80 };
81
82 assert_eq!(get_span_len("ab", "ab"), Some(2));
83 assert_eq!(get_span_len("a", "ab"), Some(1));
84 assert_eq!(get_span_len("ab", "abc"), Some(2));
85 assert_eq!(get_span_len("abc", "abcd"), Some(3));
86 assert_eq!(get_span_len("curl", "curly"), Some(4));
87 assert_eq!(get_span_len("curl", "acccccurlycurrelly"), Some(4));
88 assert_eq!(get_span_len("z", "acccccurlycurrelly"), None);
89 assert_eq!(get_span_len("ssh", "testssh"), Some(3));
90 assert_eq!(get_span_len("aba", "abababa"), Some(3));
91 assert_eq!(run_span("", "abc"), Some((0, 0))); assert_eq!(run_span("a", ""), None); assert_eq!(run_span("", ""), None); }
95
96 #[test]
97 fn test_is_subsequence_consumes_main_seq() {
98 let sub: Vec<char> = vec!['a', 'a', 'a'];
99 let main_seq: Vec<char> = vec!['a', 'a'];
100 assert!(!is_subsequence(&sub, &main_seq), "is_subsequence should return false when main_seq is consumed before sub is fully matched");
101
102 let sub_ok: Vec<char> = vec!['a', 'a'];
103 let main_seq_ok: Vec<char> = vec!['a', 'a', 'a'];
104 assert!(
105 is_subsequence(&sub_ok, &main_seq_ok),
106 "is_subsequence should return true when sub is shorter or equal and matches"
107 );
108
109 let sub_diff: Vec<char> = vec!['a', 'b'];
110 let main_seq_diff: Vec<char> = vec!['a', 'a'];
111 assert!(
112 !is_subsequence(&sub_diff, &main_seq_diff),
113 "is_subsequence should return false for non-matching elements"
114 );
115 }
116
117 fn is_subsequence<A: PartialEq>(sub: &[A], main_seq: &[A]) -> bool {
119 let mut main_iter = main_seq.iter();
120 sub.iter()
121 .all(|sub_item| main_iter.any(|main_item| main_item == sub_item))
122 }
123
124 proptest! {
126 #![proptest_config(ProptestConfig::with_cases(10000))]
127 #[test]
128 fn prop_span_finds_valid_subsequence(
129 query in proptest::collection::vec("[a-c]", 0..10),
131 history in proptest::collection::vec("[a-d]", 0..50)
133 ) {
134 if let Some((from, to)) = minspan::span(&query, &history) {
135 prop_assert!(from <= to, "from index {} > to index {}", from, to);
137 prop_assert!(to < history.len(), "to index {} is out of bounds for history len {}", to, history.len());
138
139 let history_slice = &history[from..=to];
141 prop_assert!(
142 is_subsequence(&query, history_slice),
143 "query {:?} is not a subsequence of history slice {:?} (from={}, to={})",
144 query, history_slice, from, to
145 );
146
147 for f in 0..history.len() {
157 for t in f..history.len() {
158 if t - f < to - from {
160 let shorter_slice = &history[f..=t];
161 prop_assert!(
162 !is_subsequence(&query, shorter_slice),
163 "Found shorter span history[{}:{}] ({:?}) that contains query {:?}, but span returned ({}, {}) len {}",
164 f, t, shorter_slice, query, from, to, to - from + 1
165 );
166 }
167 }
168 }
169
170 } else {
171 prop_assert!(
174 !is_subsequence(&query, &history) || query.is_empty(),
175 "span returned None, but query {:?} IS a subsequence of history {:?}",
176 query, history
177 );
178 }
179 }
180 }
181}