ferritin_core/columns/
dictionary.rs1use super::error::{BoundaryError, to_u32};
4use std::collections::HashMap;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct StringDictionary {
18 codes: Vec<u32>,
19 value_bytes: Vec<u8>,
20 value_offsets: Vec<u32>,
21}
22
23impl StringDictionary {
24 pub fn encode<'s>(values: impl IntoIterator<Item = &'s str>) -> Result<Self, BoundaryError> {
29 let mut lookup: HashMap<&'s str, u32> = HashMap::new();
30 let mut codes = Vec::new();
31 let mut value_bytes = Vec::new();
32 let mut value_offsets = vec![0u32];
33
34 for value in values {
35 let code = match lookup.get(value) {
36 Some(&code) => code,
37 None => {
38 let code = to_u32("dictionary value count", lookup.len())?;
39 value_bytes.extend_from_slice(value.as_bytes());
40 value_offsets.push(to_u32("dictionary value bytes", value_bytes.len())?);
41 lookup.insert(value, code);
42 code
43 }
44 };
45 codes.push(code);
46 }
47 to_u32("dictionary row count", codes.len())?;
48
49 Ok(Self {
50 codes,
51 value_bytes,
52 value_offsets,
53 })
54 }
55
56 pub fn codes(&self) -> &[u32] {
58 &self.codes
59 }
60
61 pub fn value_bytes(&self) -> &[u8] {
63 &self.value_bytes
64 }
65
66 pub fn value_offsets(&self) -> &[u32] {
69 &self.value_offsets
70 }
71
72 pub fn len(&self) -> usize {
74 self.codes.len()
75 }
76
77 pub fn is_empty(&self) -> bool {
79 self.codes.is_empty()
80 }
81
82 pub fn n_values(&self) -> usize {
84 self.value_offsets.len() - 1
85 }
86
87 pub fn value(&self, code: u32) -> Option<&str> {
89 let code = code as usize;
90 let start = *self.value_offsets.get(code)? as usize;
91 let end = *self.value_offsets.get(code + 1)? as usize;
92 std::str::from_utf8(&self.value_bytes[start..end]).ok()
94 }
95
96 pub fn get(&self, row: usize) -> Option<&str> {
98 self.value(*self.codes.get(row)?)
99 }
100
101 pub fn values(&self) -> impl Iterator<Item = &str> + '_ {
103 (0..self.n_values() as u32).filter_map(|code| self.value(code))
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_dictionary_first_occurrence_codes() {
113 let dict = StringDictionary::encode(["CA", "N", "CA", "C", "N"]).unwrap();
114 assert_eq!(dict.codes(), &[0, 1, 0, 2, 1]);
115 assert_eq!(dict.n_values(), 3);
116 assert_eq!(dict.value_bytes(), b"CANC");
117 assert_eq!(dict.value_offsets(), &[0, 2, 3, 4]);
118 assert_eq!(dict.values().collect::<Vec<_>>(), vec!["CA", "N", "C"]);
119 }
120
121 #[test]
122 fn test_dictionary_roundtrip_and_bounds() {
123 let input = ["ALA", "", "HOH", "ALA", "Ω"];
124 let dict = StringDictionary::encode(input).unwrap();
125 let decoded: Vec<&str> = (0..dict.len()).map(|i| dict.get(i).unwrap()).collect();
126 assert_eq!(decoded, input);
127 assert_eq!(dict.get(input.len()), None);
128 assert_eq!(dict.value(dict.n_values() as u32), None);
129 }
130
131 #[test]
132 fn test_dictionary_empty() {
133 let dict = StringDictionary::encode(std::iter::empty()).unwrap();
134 assert!(dict.is_empty());
135 assert_eq!(dict.n_values(), 0);
136 assert_eq!(dict.value_offsets(), &[0]);
137 }
138}