velesdb_core/compression/
dictionary.rs1#![allow(clippy::cast_possible_truncation)]
7#![allow(clippy::cast_precision_loss)]
8
9use rustc_hash::FxHashMap;
10use std::hash::Hash;
11use std::mem::size_of;
12
13#[derive(Debug, Clone, Default)]
15pub struct CompressionStats {
16 pub unique_values: usize,
18 pub total_values: usize,
20 pub dictionary_size_bytes: usize,
22 pub encoded_size_bytes: usize,
24 pub compression_ratio: f64,
26}
27
28#[derive(Debug, Clone)]
30pub struct DictCodebook<V> {
31 value_to_code: FxHashMap<V, u32>,
33 code_to_value: Vec<V>,
35}
36
37impl<V: Hash + Eq + Clone> Default for DictCodebook<V> {
38 fn default() -> Self {
39 Self {
40 value_to_code: FxHashMap::default(),
41 code_to_value: Vec::new(),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
50pub struct DictionaryEncoder<V: Hash + Eq + Clone> {
51 codebook: DictCodebook<V>,
53 total_encoded: usize,
55}
56
57impl<V: Hash + Eq + Clone> DictionaryEncoder<V> {
58 #[must_use]
60 pub fn new() -> Self {
61 Self {
62 codebook: DictCodebook::default(),
63 total_encoded: 0,
64 }
65 }
66
67 #[must_use]
69 pub fn is_empty(&self) -> bool {
70 self.codebook.code_to_value.is_empty()
71 }
72
73 #[must_use]
75 pub fn len(&self) -> usize {
76 self.codebook.code_to_value.len()
77 }
78
79 pub fn encode(&mut self, value: V) -> u32 {
83 self.total_encoded += 1;
84
85 if let Some(&code) = self.codebook.value_to_code.get(&value) {
86 return code;
87 }
88
89 let code = u32::try_from(self.codebook.code_to_value.len()).unwrap_or(u32::MAX);
92 self.codebook.value_to_code.insert(value.clone(), code);
93 self.codebook.code_to_value.push(value);
94 code
95 }
96
97 #[must_use]
99 pub fn decode(&self, code: u32) -> Option<&V> {
100 self.codebook.code_to_value.get(code as usize)
101 }
102
103 pub fn encode_batch(&mut self, values: &[V]) -> Vec<u32> {
105 values.iter().map(|v| self.encode(v.clone())).collect()
106 }
107
108 #[must_use]
110 pub fn decode_batch(&self, codes: &[u32]) -> Vec<V> {
111 codes
112 .iter()
113 .filter_map(|&code| self.decode(code).cloned())
114 .collect()
115 }
116
117 pub fn clear(&mut self) {
119 self.codebook.value_to_code.clear();
120 self.codebook.code_to_value.clear();
121 self.total_encoded = 0;
122 }
123
124 #[must_use]
126 pub fn stats(&self) -> CompressionStats {
127 let unique = self.len();
128 let total = self.total_encoded;
129
130 let value_size = size_of::<V>();
132 let original_size = total * value_size;
133 let dict_size = unique * value_size + unique * 4; let encoded_size = total * 4; let compressed_size = dict_size + encoded_size;
136
137 let ratio = if compressed_size > 0 {
138 original_size as f64 / compressed_size as f64
139 } else {
140 0.0
141 };
142
143 CompressionStats {
144 unique_values: unique,
145 total_values: total,
146 dictionary_size_bytes: dict_size,
147 encoded_size_bytes: encoded_size,
148 compression_ratio: ratio,
149 }
150 }
151
152 #[must_use]
154 pub fn codebook(&self) -> &DictCodebook<V> {
155 &self.codebook
156 }
157}
158
159impl<V: Hash + Eq + Clone> Default for DictionaryEncoder<V> {
160 fn default() -> Self {
161 Self::new()
162 }
163}