1use std::cell::RefCell;
46use std::sync::OnceLock;
47
48use rudb_common::{Error, Result};
49
50pub const ESCAPE: u8 = 255;
53
54pub const MAX_SYMBOLS: usize = 255;
56
57pub const MAX_SYMBOL_LEN: usize = 8;
60
61const GENERATIONS: usize = 5;
63
64const HASH_SLOTS: usize = 1024;
67
68const PROBE: usize = 8;
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
74struct Symbol {
75 value: u64,
76 len: u8,
77}
78
79impl Symbol {
80 fn new(bytes: &[u8]) -> Self {
81 let len = bytes.len().min(MAX_SYMBOL_LEN);
82 let mut value = 0u64;
83 for (index, byte) in bytes[..len].iter().enumerate() {
84 value |= u64::from(*byte) << (8 * index);
85 }
86 Self { value, len: len as u8 }
87 }
88
89 fn single(byte: u8) -> Self {
90 Self { value: u64::from(byte), len: 1 }
91 }
92
93 fn len(self) -> usize {
94 self.len as usize
95 }
96
97 fn mask(self) -> u64 {
98 mask_of(self.len())
99 }
100
101 fn bytes(self) -> Vec<u8> {
102 (0..self.len()).map(|index| (self.value >> (8 * index)) as u8).collect()
103 }
104
105 fn concat(self, other: Self) -> Self {
107 if self.len() >= MAX_SYMBOL_LEN {
108 return self;
109 }
110 let len = (self.len() + other.len()).min(MAX_SYMBOL_LEN);
111 let value = self.value | (other.value << (8 * self.len()));
112 Self { value: value & mask_of(len), len: len as u8 }
113 }
114}
115
116fn mask_of(len: usize) -> u64 {
117 if len >= 8 { u64::MAX } else { (1u64 << (8 * len)) - 1 }
118}
119
120pub struct SymbolTable {
122 symbols: Vec<Symbol>,
124 lookup: OnceLock<Lookup>,
131}
132
133struct Lookup {
135 single: Vec<u8>,
137 pair: Vec<u16>,
139 hash: Vec<Option<(Symbol, u8)>>,
141}
142
143impl std::fmt::Debug for SymbolTable {
144 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 formatter
147 .debug_struct("SymbolTable")
148 .field("symbols", &self.symbols.len())
149 .field("bytes", &self.serialized_len())
150 .finish()
151 }
152}
153
154impl PartialEq for SymbolTable {
161 fn eq(&self, other: &Self) -> bool {
162 self.symbols == other.symbols
163 }
164}
165
166impl Eq for SymbolTable {}
167
168impl SymbolTable {
169 #[must_use]
176 pub fn footprint(&self) -> usize {
177 size_of::<Self>()
178 + self.symbols.capacity() * size_of::<Symbol>()
179 + self.lookup.get().map_or(0, |lookup| {
180 lookup.single.capacity()
181 + lookup.pair.capacity() * size_of::<u16>()
182 + lookup.hash.capacity() * size_of::<Option<(Symbol, u8)>>()
183 })
184 }
185
186 #[must_use]
189 pub fn empty() -> Self {
190 Self::build(Vec::new())
191 }
192
193 #[must_use]
199 pub fn train(samples: &[&[u8]]) -> Self {
200 thread_local! {
204 static COUNTS: RefCell<Option<Counts>> = const { RefCell::new(None) };
205 }
206 COUNTS.with(|held| match held.try_borrow_mut() {
207 Ok(mut held) => Self::train_with(samples, held.get_or_insert_with(Counts::new)),
208 Err(_) => Self::train_with(samples, &mut Counts::new()),
209 })
210 }
211
212 fn train_with(samples: &[&[u8]], counts: &mut Counts) -> Self {
213 let mut table = Self::empty();
214 for _ in 0..GENERATIONS {
215 counts.clear();
216 for sample in samples {
217 table.count(sample, counts);
218 }
219 let next = counts.best(&table);
220 if next.is_empty() {
221 break;
222 }
223 table = Self::build(next);
224 }
225 table
226 }
227
228 #[must_use]
230 pub fn len(&self) -> usize {
231 self.symbols.len()
232 }
233
234 #[must_use]
236 pub fn is_empty(&self) -> bool {
237 self.symbols.is_empty()
238 }
239
240 #[must_use]
244 pub fn serialized_len(&self) -> usize {
245 1 + self.symbols.iter().map(|symbol| 1 + symbol.len()).sum::<usize>()
246 }
247
248 pub fn serialize(&self, out: &mut Vec<u8>) {
250 out.push(self.symbols.len() as u8);
251 for symbol in &self.symbols {
252 out.push(symbol.len);
253 out.extend_from_slice(&symbol.bytes());
254 }
255 }
256
257 pub fn deserialize(bytes: &[u8]) -> Result<(Self, usize)> {
263 let count = *bytes.first().ok_or_else(|| truncated("a symbol table header"))? as usize;
264 let mut at = 1;
265 let mut symbols = Vec::with_capacity(count);
266 for _ in 0..count {
267 let len = *bytes.get(at).ok_or_else(|| truncated("a symbol length"))? as usize;
268 if len == 0 || len > MAX_SYMBOL_LEN {
269 return Err(Error::internal(format!("a symbol of {len} bytes is not a symbol")));
270 }
271 at += 1;
272 let end = at + len;
273 if end > bytes.len() {
274 return Err(truncated("a symbol"));
275 }
276 symbols.push(Symbol::new(&bytes[at..end]));
277 at = end;
278 }
279 Ok((Self::build(symbols), at))
280 }
281
282 pub fn compress(&self, input: &[u8], out: &mut Vec<u8>) {
288 let mut at = 0;
289 while at < input.len() {
290 let (code, len) = self.match_at(input, at);
291 if code == ESCAPE {
292 out.push(ESCAPE);
293 out.push(input[at]);
294 } else {
295 out.push(code);
296 }
297 at += len;
298 }
299 }
300
301 pub fn decompress(&self, input: &[u8], out: &mut Vec<u8>) -> Result<()> {
318 out.reserve(input.len().saturating_mul(2).saturating_add(MAX_SYMBOL_LEN));
322 let mut at = 0;
323 while at < input.len() {
324 let code = input[at];
325 at += 1;
326 if code == ESCAPE {
327 let literal = *input.get(at).ok_or_else(|| truncated("an escaped byte"))?;
328 out.push(literal);
329 at += 1;
330 } else {
331 let symbol = *self
332 .symbols
333 .get(code as usize)
334 .ok_or_else(|| Error::internal(format!("code {code} is not in the table")))?;
335 out.extend_from_slice(&symbol.value.to_le_bytes());
336 out.truncate(out.len() - (MAX_SYMBOL_LEN - symbol.len()));
337 }
338 }
339 Ok(())
340 }
341
342 #[inline]
357 pub fn decompress_at(&self, input: &[u8], out: &mut [u8], mut at: usize) -> Result<usize> {
358 let symbols = self.symbols.as_slice();
359 let mut codes = input.iter();
360 while let Some(&code) = codes.next() {
361 if code == ESCAPE {
362 let Some(&literal) = codes.next() else {
363 return Err(truncated("an escaped byte"));
364 };
365 let Some(slot) = out.get_mut(at) else {
366 return Err(out_of_room());
367 };
368 *slot = literal;
369 at += 1;
370 } else {
371 let Some(symbol) = symbols.get(code as usize) else {
372 return Err(not_in_table(code));
373 };
374 let Some(slot) = out.get_mut(at..at + MAX_SYMBOL_LEN) else {
375 return Err(out_of_room());
376 };
377 slot.copy_from_slice(&symbol.value.to_le_bytes());
378 at += symbol.len();
379 }
380 }
381 Ok(at)
382 }
383
384 fn match_at(&self, input: &[u8], at: usize) -> (u8, usize) {
386 let remaining = input.len() - at;
387 let word = load(input, at);
388 if remaining >= 3 {
391 if let Some((symbol, code)) = self.probe(word, remaining) {
392 return (code, symbol.len());
393 }
394 }
395 if remaining >= 2 {
396 let code = self.lookup().pair[(word & 0xffff) as usize];
397 if code != u16::MAX {
398 return (code as u8, 2);
399 }
400 }
401 let code = self.lookup().single[(word & 0xff) as usize];
402 if code == ESCAPE { (ESCAPE, 1) } else { (code, 1) }
403 }
404
405 fn probe(&self, word: u64, remaining: usize) -> Option<(Symbol, u8)> {
411 let hash = &self.lookup().hash;
412 let mut slot = hash_of(word);
413 let mut best: Option<(Symbol, u8)> = None;
414 for _ in 0..PROBE {
415 match hash[slot] {
416 None => break,
417 Some((symbol, code)) => {
418 if symbol.len() <= remaining
419 && word & symbol.mask() == symbol.value
420 && best.is_none_or(|(found, _)| symbol.len() > found.len())
421 {
422 best = Some((symbol, code));
423 }
424 }
425 }
426 slot = (slot + 1) & (HASH_SLOTS - 1);
427 }
428 best
429 }
430
431 fn count(&self, input: &[u8], counts: &mut Counts) {
434 let mut at = 0;
435 let mut previous: Option<u16> = None;
436 while at < input.len() {
437 let (code, len) = self.match_at(input, at);
438 let id = if code == ESCAPE { 256 + u16::from(input[at]) } else { u16::from(code) };
439 counts.one(id);
440 if let Some(previous) = previous {
441 counts.two(previous, id);
442 }
443 previous = Some(id);
444 at += len;
445 }
446 }
447
448 fn build(symbols: Vec<Symbol>) -> Self {
449 Self { symbols, lookup: OnceLock::new() }
450 }
451
452 fn lookup(&self) -> &Lookup {
453 self.lookup.get_or_init(|| Lookup::of(&self.symbols))
454 }
455}
456
457impl Lookup {
458 fn of(symbols: &[Symbol]) -> Self {
459 let mut table = Self {
460 single: vec![ESCAPE; 256],
461 pair: vec![u16::MAX; 65536],
462 hash: vec![None; HASH_SLOTS],
463 };
464 let mut order: Vec<(Symbol, u8)> =
467 symbols.iter().enumerate().map(|(code, symbol)| (*symbol, code as u8)).collect();
468 order.sort_by_key(|(symbol, code)| (std::cmp::Reverse(symbol.len()), *code));
469 for (symbol, code) in order {
470 match symbol.len() {
471 1 => {
472 let index = (symbol.value & 0xff) as usize;
473 if table.single[index] == ESCAPE {
474 table.single[index] = code;
475 }
476 }
477 2 => {
478 let index = (symbol.value & 0xffff) as usize;
479 if table.pair[index] == u16::MAX {
480 table.pair[index] = u16::from(code);
481 }
482 }
483 _ => {
484 let mut slot = hash_of(symbol.value);
485 for _ in 0..PROBE {
486 if table.hash[slot].is_none() {
487 table.hash[slot] = Some((symbol, code));
488 break;
489 }
490 slot = (slot + 1) & (HASH_SLOTS - 1);
491 }
492 }
493 }
494 }
495 table
496 }
497}
498
499fn load(input: &[u8], at: usize) -> u64 {
504 if at + 8 <= input.len() {
505 let bytes: [u8; 8] = input[at..at + 8].try_into().expect("eight bytes were checked");
506 u64::from_le_bytes(bytes)
507 } else {
508 let mut word = 0u64;
509 for (index, byte) in input[at..].iter().enumerate() {
510 word |= u64::from(*byte) << (8 * index);
511 }
512 word
513 }
514}
515
516fn hash_of(word: u64) -> usize {
520 let key = word & 0xff_ffff;
521 ((key.wrapping_mul(0x9e37_79b9_7f4a_7c15)) >> (64 - HASH_SLOTS.trailing_zeros())) as usize
522}
523
524struct Counts {
528 single: Vec<u32>,
529 pairs: Vec<u32>,
532 seen: Vec<u32>,
535 gains: Gains,
537}
538
539#[derive(Default)]
541struct Gains {
542 gains: Vec<(Symbol, u64)>,
543 places: Vec<u32>,
545 taken: Vec<u32>,
547}
548
549impl Gains {
550 fn clear(&mut self, most: usize) {
552 for place in self.taken.drain(..) {
553 self.places[place as usize] = u32::MAX;
554 }
555 let wanted = (most * 2).next_power_of_two();
556 if self.places.len() < wanted {
557 self.places = vec![u32::MAX; wanted];
558 }
559 self.gains.clear();
560 }
561
562 fn add(&mut self, symbol: Symbol, count: u64) {
564 let gain = count * symbol.len() as u64;
565 let mask = self.places.len() - 1;
566 let mut place = gain_hash(symbol) & mask;
567 loop {
568 let at = self.places[place];
569 if at == u32::MAX {
570 self.places[place] = self.gains.len() as u32;
571 self.taken.push(place as u32);
572 self.gains.push((symbol, gain));
573 return;
574 }
575 if self.gains[at as usize].0 == symbol {
576 self.gains[at as usize].1 += gain;
577 return;
578 }
579 place = (place + 1) & mask;
580 }
581 }
582}
583
584const IDS: usize = 512;
586
587impl Counts {
588 fn new() -> Self {
589 Self {
590 single: vec![0; IDS],
591 pairs: vec![0; IDS * IDS],
592 seen: Vec::new(),
593 gains: Gains::default(),
594 }
595 }
596
597 fn clear(&mut self) {
598 self.single.fill(0);
599 for slot in self.seen.drain(..) {
600 self.pairs[slot as usize] = 0;
601 }
602 }
603
604 fn one(&mut self, id: u16) {
605 self.single[id as usize] += 1;
606 }
607
608 fn two(&mut self, first: u16, second: u16) {
609 let slot = first as usize * IDS + second as usize;
610 if self.pairs[slot] == 0 {
611 self.seen.push(slot as u32);
612 }
613 self.pairs[slot] += 1;
614 }
615
616 fn best(&mut self, table: &SymbolTable) -> Vec<Symbol> {
623 self.gains.clear(IDS + self.seen.len());
628 for (id, count) in self.single.iter().enumerate() {
629 if *count == 0 {
630 continue;
631 }
632 let symbol = symbol_of(table, id as u16);
633 self.gains.add(symbol, u64::from(*count));
634 }
635 for slot in &self.seen {
636 let slot = *slot as usize;
637 let (first, second) = ((slot / IDS) as u16, (slot % IDS) as u16);
638 let symbol = symbol_of(table, first).concat(symbol_of(table, second));
639 self.gains.add(symbol, u64::from(self.pairs[slot]));
640 }
641 let gains = &mut self.gains.gains;
642 let order = |left: &(Symbol, u64), right: &(Symbol, u64)| {
646 right.1.cmp(&left.1).then(left.0.cmp(&right.0))
647 };
648 if gains.len() > MAX_SYMBOLS {
649 gains.select_nth_unstable_by(MAX_SYMBOLS - 1, order);
650 gains.truncate(MAX_SYMBOLS);
651 }
652 gains.sort_unstable_by(order);
653 gains.iter().map(|(symbol, _)| *symbol).collect()
654 }
655}
656
657fn gain_hash(symbol: Symbol) -> usize {
659 ((symbol.value ^ u64::from(symbol.len)).wrapping_mul(0x9e37_79b9_7f4a_7c15) >> 32) as usize
660}
661
662fn symbol_of(table: &SymbolTable, id: u16) -> Symbol {
663 if (id as usize) < table.symbols.len() {
664 table.symbols[id as usize]
665 } else {
666 Symbol::single((id.saturating_sub(256)) as u8)
667 }
668}
669
670#[cold]
671fn not_in_table(code: u8) -> Error {
672 Error::internal(format!("code {code} is not in the table"))
673}
674
675#[cold]
676fn out_of_room() -> Error {
677 Error::internal("a string decompresses to more than its length says")
678}
679
680#[cold]
681fn truncated(what: &str) -> Error {
682 Error::internal(format!("the input ended in the middle of {what}"))
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688
689 #[test]
690 fn a_table_read_back_to_decompress_builds_no_lookup_tables() {
691 let urls = urls();
692 let samples: Vec<&[u8]> = urls.iter().map(Vec::as_slice).collect();
693 let trained = SymbolTable::train(&samples);
694 let mut compressed = Vec::new();
695 trained.compress(&urls[7], &mut compressed);
696 let mut stored = Vec::new();
697 trained.serialize(&mut stored);
698 let (read, _) = SymbolTable::deserialize(&stored).expect("a table it wrote");
699 let mut out = Vec::new();
700 read.decompress(&compressed, &mut out).expect("a string it compressed");
701 assert_eq!(out, urls[7]);
702 assert!(read.lookup.get().is_none(), "decompressing reads the symbols alone");
703 assert!(read.footprint() < trained.footprint());
704 }
705
706 fn urls() -> Vec<Vec<u8>> {
710 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
711 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
712 let mut out = Vec::new();
713 for index in 0..600 {
714 let host = hosts[index % hosts.len()];
715 let path = paths[(index / 3) % paths.len()];
716 out.push(
717 format!("http://{host}{path}?session={}&ref=google&page={}", index * 7, index % 20)
718 .into_bytes(),
719 );
720 }
721 out
722 }
723
724 fn borrow(strings: &[Vec<u8>]) -> Vec<&[u8]> {
725 strings.iter().map(Vec::as_slice).collect()
726 }
727
728 fn round_trip(table: &SymbolTable, strings: &[Vec<u8>]) -> (usize, usize) {
729 let mut raw = 0;
730 let mut compressed = 0;
731 for string in strings {
732 let mut bytes = Vec::new();
733 table.compress(string, &mut bytes);
734 let mut back = Vec::new();
735 table.decompress(&bytes, &mut back).unwrap();
736 assert_eq!(back, *string, "{}", String::from_utf8_lossy(string));
737 raw += string.len();
738 compressed += bytes.len();
739 }
740 (raw, compressed)
741 }
742
743 #[test]
744 fn urls_compress_by_more_than_half_and_come_back_unchanged() {
745 let strings = urls();
748 let table = SymbolTable::train(&borrow(&strings));
749 let (raw, compressed) = round_trip(&table, &strings);
750 let ratio = raw as f64 / compressed as f64;
751 assert!(ratio > 2.5, "{ratio:.2}x, {raw} to {compressed}");
752 assert!(table.len() > 100, "{} symbols", table.len());
753 }
754
755 #[test]
756 fn the_trainer_finds_the_long_repeated_pieces() {
757 let strings = urls();
758 let table = SymbolTable::train(&borrow(&strings));
759 let found: Vec<String> = (0..table.len())
760 .map(|code| String::from_utf8_lossy(&table.symbols[code].bytes()).into_owned())
761 .collect();
762 let long = found.iter().filter(|symbol| symbol.len() >= 6).count();
765 assert!(long > 60, "only {long} symbols of six bytes or more: {found:?}");
766 }
767
768 #[test]
769 fn english_text_round_trips_and_shrinks() {
770 let text: Vec<Vec<u8>> = "the quick brown fox jumps over the lazy dog while the other dog \
771 watches the fox and the dog and the fox go over the hill together"
772 .split(' ')
773 .map(|word| word.as_bytes().to_vec())
774 .collect();
775 let table = SymbolTable::train(&borrow(&text));
776 let (raw, compressed) = round_trip(&table, &text);
777 assert!(compressed < raw, "{raw} to {compressed}");
778 }
779
780 #[test]
781 fn incompressible_bytes_round_trip_and_cost_what_escaping_costs() {
782 let mut state = 0x1234_5678_9abc_def0u64;
786 let strings: Vec<Vec<u8>> = (0..100)
787 .map(|_| {
788 (0..64)
789 .map(|_| {
790 state ^= state << 13;
791 state ^= state >> 7;
792 state ^= state << 17;
793 state as u8
794 })
795 .collect()
796 })
797 .collect();
798 let table = SymbolTable::train(&borrow(&strings));
799 let (raw, compressed) = round_trip(&table, &strings);
800 assert!(compressed < raw * 2, "{raw} to {compressed}");
801 }
802
803 #[test]
804 fn an_empty_table_escapes_everything_and_still_round_trips() {
805 let table = SymbolTable::empty();
806 let strings = vec![b"hello".to_vec(), Vec::new(), b"x".to_vec()];
807 let (raw, compressed) = round_trip(&table, &strings);
808 assert_eq!(compressed, raw * 2);
809 }
810
811 #[test]
812 fn an_empty_string_compresses_to_nothing() {
813 let table = SymbolTable::train(&[b"abcabcabc"]);
814 let mut out = Vec::new();
815 table.compress(b"", &mut out);
816 assert!(out.is_empty());
817 let mut back = Vec::new();
818 table.decompress(&out, &mut back).unwrap();
819 assert!(back.is_empty());
820 }
821
822 #[test]
823 fn a_string_shorter_than_the_symbols_does_not_read_past_its_end() {
824 let table = SymbolTable::train(&[b"ab\0ab\0ab\0ab\0", b"abcdefgh"]);
827 for string in [b"a".to_vec(), b"ab".to_vec(), b"abc".to_vec()] {
828 let mut bytes = Vec::new();
829 table.compress(&string, &mut bytes);
830 let mut back = Vec::new();
831 table.decompress(&bytes, &mut back).unwrap();
832 assert_eq!(back, string);
833 }
834 }
835
836 #[test]
837 fn a_table_survives_being_written_and_read_back() {
838 let strings = urls();
839 let table = SymbolTable::train(&borrow(&strings));
840 let mut bytes = Vec::new();
841 table.serialize(&mut bytes);
842 assert_eq!(bytes.len(), table.serialized_len());
843 let (read, consumed) = SymbolTable::deserialize(&bytes).unwrap();
844 assert_eq!(consumed, bytes.len());
845 assert_eq!(read.symbols, table.symbols);
846
847 let mut first = Vec::new();
850 let mut second = Vec::new();
851 table.compress(&strings[7], &mut first);
852 read.compress(&strings[7], &mut second);
853 assert_eq!(first, second);
854 }
855
856 #[test]
857 fn a_full_table_is_two_kilobytes_at_the_very_most() {
858 let strings = urls();
859 let table = SymbolTable::train(&borrow(&strings));
860 assert!(table.serialized_len() <= 1 + MAX_SYMBOLS * (1 + MAX_SYMBOL_LEN));
861 assert!(table.serialized_len() <= 2049);
862 }
863
864 #[test]
865 fn a_truncated_symbol_table_is_an_error() {
866 let strings = urls();
867 let table = SymbolTable::train(&borrow(&strings));
868 let mut bytes = Vec::new();
869 table.serialize(&mut bytes);
870 for len in 1..bytes.len().min(40) {
871 let error = SymbolTable::deserialize(&bytes[..len]).unwrap_err();
872 assert!(error.message().contains("ended in the middle"), "{error}");
873 }
874 }
875
876 #[test]
877 fn a_symbol_of_zero_bytes_is_an_error() {
878 let error = SymbolTable::deserialize(&[1, 0]).unwrap_err();
879 assert!(error.message().contains("is not a symbol"), "{error}");
880 }
881
882 #[test]
883 fn a_short_symbol_does_not_drag_the_rest_of_its_word_out_with_it() {
884 let table = SymbolTable::train(&[b"abcdabcdabcdabcd"]);
890 let mut compressed = Vec::new();
891 table.compress(b"abcdabcd", &mut compressed);
892 let mut out = Vec::new();
893 table.decompress(&compressed, &mut out).expect("decompresses");
894 table.decompress(&compressed, &mut out).expect("decompresses");
895 assert_eq!(out, b"abcdabcdabcdabcd");
896 }
897
898 #[test]
899 fn a_dangling_escape_is_an_error_and_not_a_panic() {
900 let table = SymbolTable::train(&[b"abcabcabc"]);
901 let error = table.decompress(&[ESCAPE], &mut Vec::new()).unwrap_err();
902 assert!(error.message().contains("escaped byte"), "{error}");
903 }
904
905 #[test]
906 fn a_code_the_table_does_not_have_is_an_error() {
907 let table = SymbolTable::train(&[b"abcabcabc"]);
908 let code = table.len() as u8;
909 let error = table.decompress(&[code], &mut Vec::new()).unwrap_err();
910 assert!(error.message().contains("not in the table"), "{error}");
911 }
912
913 #[test]
914 fn training_twice_on_the_same_sample_gives_the_same_table() {
915 let strings = urls();
917 let first = SymbolTable::train(&borrow(&strings));
918 let second = SymbolTable::train(&borrow(&strings));
919 assert_eq!(first.symbols, second.symbols);
920 }
921
922 #[test]
923 fn the_longest_match_wins_rather_than_the_first_one_found() {
924 let table = SymbolTable::build(vec![
925 Symbol::new(b"abc"),
926 Symbol::new(b"abcdef"),
927 Symbol::new(b"abcd"),
928 ]);
929 let mut out = Vec::new();
930 table.compress(b"abcdef", &mut out);
931 assert_eq!(out, vec![1]);
932 }
933
934 #[test]
935 fn a_symbol_longer_than_what_is_left_is_not_used() {
936 let table = SymbolTable::build(vec![Symbol::new(b"abcdef"), Symbol::new(b"ab")]);
937 let mut out = Vec::new();
938 table.compress(b"abcd", &mut out);
939 assert_eq!(out, vec![1, ESCAPE, b'c', ESCAPE, b'd']);
941 }
942
943 #[test]
944 fn concatenation_stops_at_eight_bytes() {
945 let long = Symbol::new(b"abcdef");
946 assert_eq!(long.concat(Symbol::new(b"ghijkl")).bytes(), b"abcdefgh");
947 assert_eq!(Symbol::new(b"ab").concat(Symbol::new(b"cd")).bytes(), b"abcd");
948 }
949
950 fn train_with_maps(samples: &[&[u8]]) -> SymbolTable {
953 use std::collections::HashMap;
954 let mut table = SymbolTable::empty();
955 for _ in 0..GENERATIONS {
956 let mut single = [0u32; IDS];
957 let mut pairs: HashMap<(u16, u16), u32> = HashMap::new();
958 for sample in samples {
959 let mut at = 0;
960 let mut previous: Option<u16> = None;
961 while at < sample.len() {
962 let (code, len) = table.match_at(sample, at);
963 let id =
964 if code == ESCAPE { 256 + u16::from(sample[at]) } else { u16::from(code) };
965 single[id as usize] += 1;
966 if let Some(previous) = previous {
967 *pairs.entry((previous, id)).or_insert(0) += 1;
968 }
969 previous = Some(id);
970 at += len;
971 }
972 }
973 let mut gains: HashMap<Symbol, u64> = HashMap::new();
974 for (id, count) in single.iter().enumerate().filter(|(_, count)| **count > 0) {
975 let symbol = symbol_of(&table, id as u16);
976 *gains.entry(symbol).or_insert(0) += u64::from(*count) * symbol.len() as u64;
977 }
978 for ((first, second), count) in &pairs {
979 let symbol = symbol_of(&table, *first).concat(symbol_of(&table, *second));
980 *gains.entry(symbol).or_insert(0) += u64::from(*count) * symbol.len() as u64;
981 }
982 let mut ranked: Vec<(Symbol, u64)> = gains.into_iter().collect();
983 ranked.sort_by(|left, right| right.1.cmp(&left.1).then(left.0.cmp(&right.0)));
984 ranked.truncate(MAX_SYMBOLS);
985 if ranked.is_empty() {
986 break;
987 }
988 table = SymbolTable::build(ranked.into_iter().map(|(symbol, _)| symbol).collect());
989 }
990 table
991 }
992
993 #[test]
994 fn flat_counts_train_the_same_table_as_hash_maps() {
995 let mut state = 0x9e37_79b9_7f4a_7c15u64;
996 let mut next = move || {
997 state ^= state << 13;
998 state ^= state >> 7;
999 state ^= state << 17;
1000 state
1001 };
1002 let mut shapes: Vec<Vec<Vec<u8>>> = vec![urls(), Vec::new(), vec![Vec::new(); 3]];
1003 shapes.push(
1005 (0..400).map(|_| (0..12).map(|_| b"abc"[(next() % 3) as usize]).collect()).collect(),
1006 );
1007 shapes.push((0..300).map(|_| (0..40).map(|_| next() as u8).collect()).collect());
1009 shapes.push(
1011 (0..200)
1012 .map(|index| format!("prefix-{}-suffix-{}", index % 7, index % 5).into_bytes())
1013 .collect(),
1014 );
1015 for strings in &shapes {
1018 let samples = borrow(strings);
1019 assert_eq!(SymbolTable::train(&samples).symbols, train_with_maps(&samples).symbols);
1020 }
1021 }
1022}