rudb_encoding/
sequence.rs1use rudb_common::{Error, Result};
17
18use crate::fsst::{ESCAPE, SymbolTable};
19
20const UNKNOWN: u8 = u8::MAX;
23
24#[derive(Debug, Clone)]
26pub struct Sequence {
27 next: Vec<u8>,
30 done: u8,
32}
33
34impl Sequence {
35 #[must_use]
41 pub fn new(pieces: &[&[u8]]) -> Option<Self> {
42 let pieces: Vec<&[u8]> = pieces.iter().copied().filter(|piece| !piece.is_empty()).collect();
43 let total: usize = pieces.iter().map(|piece| piece.len()).sum();
44 if pieces.is_empty() || total >= usize::from(UNKNOWN) {
45 return None;
46 }
47 let states = total + 1;
48 let mut next = vec![0_u8; states * 256];
49 let mut base = 0;
53 for piece in &pieces {
54 let first = usize::from(piece[0]);
55 for byte in 0..256 {
56 next[base * 256 + byte] = base as u8;
57 }
58 next[base * 256 + first] = (base + 1) as u8;
59 let mut restart = base;
60 for (offset, &byte) in piece.iter().enumerate().skip(1) {
61 let state = base + offset;
62 let (before, row) = next.split_at_mut(state * 256);
63 row[..256].copy_from_slice(&before[restart * 256..restart * 256 + 256]);
64 row[usize::from(byte)] = (state + 1) as u8;
65 restart = usize::from(next[restart * 256 + usize::from(byte)]);
66 }
67 base += piece.len();
68 }
69 for byte in 0..256 {
70 next[total * 256 + byte] = total as u8;
71 }
72 Some(Self { next, done: total as u8 })
73 }
74
75 fn states(&self) -> usize {
76 usize::from(self.done) + 1
77 }
78
79 #[must_use]
81 pub fn holds(&self, text: &[u8]) -> bool {
82 let mut state = 0_u8;
83 for &byte in text {
84 state = self.next[usize::from(state) * 256 + usize::from(byte)];
85 if state == self.done {
86 return true;
87 }
88 }
89 false
90 }
91
92 #[must_use]
94 pub fn over<'a>(&'a self, table: &'a SymbolTable) -> Coded<'a> {
95 Coded { sequence: self, table, steps: vec![UNKNOWN; self.states() * 256] }
96 }
97}
98
99#[derive(Debug)]
102pub struct Coded<'a> {
103 sequence: &'a Sequence,
104 table: &'a SymbolTable,
105 steps: Vec<u8>,
108}
109
110impl Coded<'_> {
111 pub fn holds(&mut self, codes: &[u8]) -> Result<bool> {
117 let done = self.sequence.done;
118 let mut state = 0_u8;
119 let mut at = 0;
120 while at < codes.len() {
121 let code = codes[at];
122 at += 1;
123 let slot = usize::from(state) * 256 + usize::from(code);
124 state = if code == ESCAPE {
125 let Some(&byte) = codes.get(at) else {
126 return Err(Error::internal("a compressed string ends in an escape"));
127 };
128 at += 1;
129 self.sequence.next[usize::from(state) * 256 + usize::from(byte)]
130 } else if self.steps[slot] != UNKNOWN {
131 self.steps[slot]
132 } else {
133 self.learn(state, code)?
134 };
135 if state == done {
136 return Ok(true);
137 }
138 }
139 Ok(false)
140 }
141
142 #[cold]
143 fn learn(&mut self, from: u8, code: u8) -> Result<u8> {
144 let Some((bytes, len)) = self.table.symbol(code) else {
145 return Err(Error::internal(format!("code {code} is not in the table")));
146 };
147 let mut state = from;
148 for &byte in &bytes[..len] {
149 state = self.sequence.next[usize::from(state) * 256 + usize::from(byte)];
150 }
151 self.steps[usize::from(from) * 256 + usize::from(code)] = state;
152 Ok(state)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn searched(text: &[u8], pieces: &[&[u8]]) -> bool {
162 let mut rest = text;
163 for piece in pieces {
164 if piece.is_empty() {
165 continue;
166 }
167 match rest.windows(piece.len()).position(|window| window == *piece) {
168 Some(at) => rest = &rest[at + piece.len()..],
169 None => return false,
170 }
171 }
172 true
173 }
174
175 fn texts() -> Vec<Vec<u8>> {
176 let words = [
177 "special",
178 "requests",
179 "spec",
180 "specia",
181 "ial",
182 "requ",
183 "sts",
184 "the",
185 "furiously",
186 "aaa",
187 "aab",
188 "ab",
189 "é",
190 "ü",
191 "",
192 "s",
193 ];
194 let mut texts = Vec::new();
195 let mut seed = 7_u64;
196 for _ in 0..3000 {
197 let mut text = Vec::new();
198 seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
199 for step in 0..(seed >> 60) {
200 let pick = (seed >> (step * 4 % 56)) as usize % words.len();
201 text.extend_from_slice(words[pick].as_bytes());
202 if (seed >> (step % 60)) & 1 == 1 {
203 text.push(b' ');
204 }
205 }
206 texts.push(text);
207 }
208 texts
209 }
210
211 #[test]
212 fn the_walk_over_bytes_and_the_walk_over_codes_agree_with_a_search() {
213 let texts = texts();
214 let samples: Vec<&[u8]> = texts.iter().map(Vec::as_slice).collect();
215 let table = SymbolTable::train(&samples);
216 let patterns: [&[&[u8]]; 7] = [
217 &[b"special", b"requests"],
218 &[b"aab"],
219 &[b"ab", b"ab", b"ab"],
220 &[b"s", b"s"],
221 &["é".as_bytes(), b"ial"],
222 &[b"", b"spec", b""],
223 &[b"furiously the", b"sts"],
224 ];
225 let mut found = 0;
226 for pieces in patterns {
227 let sequence = Sequence::new(pieces).expect("something to find");
228 let mut coded = sequence.over(&table);
229 for text in &texts {
230 let wanted = searched(text, pieces);
231 found += usize::from(wanted);
232 assert_eq!(sequence.holds(text), wanted, "{pieces:?} in {text:?}");
233 let mut codes = Vec::new();
234 table.compress(text, &mut codes);
235 assert_eq!(coded.holds(&codes).expect("codes"), wanted, "{pieces:?} in {text:?}");
236 }
237 }
238 assert!(found > 1000, "only {found} matches");
239 }
240
241 #[test]
242 fn nothing_to_find_or_too_much_is_no_automaton() {
243 assert!(Sequence::new(&[]).is_none());
244 assert!(Sequence::new(&[b"", b""]).is_none());
245 assert!(Sequence::new(&[&[b'x'; 255]]).is_none());
246 assert!(Sequence::new(&[&[b'x'; 254]]).is_some());
247 }
248}