042a294452a2e572bd4dc95ed8b890b77269a839
[pspp] / rust / src / dictionary.rs
1 use std::{
2     cmp::Ordering,
3     collections::{HashMap, HashSet},
4     fmt::Debug,
5     ops::{Bound, RangeBounds},
6 };
7
8 use encoding_rs::Encoding;
9 use indexmap::IndexSet;
10 use num::integer::div_ceil;
11 use ordered_float::OrderedFloat;
12
13 use crate::{
14     format::Spec,
15     identifier::{ByIdentifier, HasIdentifier, Identifier},
16     raw::{self, Alignment, CategoryLabels, Decoder, Measure, MissingValues, RawStr, VarType},
17 };
18
19 pub type DictIndex = usize;
20
21 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
22 pub enum VarWidth {
23     Numeric,
24     String(u16),
25 }
26
27 impl PartialOrd for VarWidth {
28     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
29         match (self, other) {
30             (VarWidth::Numeric, VarWidth::Numeric) => Some(Ordering::Equal),
31             (VarWidth::String(a), VarWidth::String(b)) => Some(a.cmp(b)),
32             _ => None,
33         }
34     }
35 }
36
37 impl VarWidth {
38     pub const MAX_STRING: u16 = 32767;
39
40     pub fn n_dict_indexes(self) -> usize {
41         match self {
42             VarWidth::Numeric => 1,
43             VarWidth::String(w) => div_ceil(w as usize, 8),
44         }
45     }
46
47     fn width_predicate(
48         a: Option<VarWidth>,
49         b: Option<VarWidth>,
50         f: impl Fn(u16, u16) -> u16,
51     ) -> Option<VarWidth> {
52         match (a, b) {
53             (Some(VarWidth::Numeric), Some(VarWidth::Numeric)) => Some(VarWidth::Numeric),
54             (Some(VarWidth::String(a)), Some(VarWidth::String(b))) => {
55                 Some(VarWidth::String(f(a, b)))
56             }
57             _ => None,
58         }
59     }
60
61     /// Returns the wider of `self` and `other`:
62     /// - Numerical variable widths are equally wide.
63     /// - Longer strings are wider than shorter strings.
64     /// - Numerical and string types are incomparable, so result in `None`.
65     /// - Any `None` in the input yields `None` in the output.
66     pub fn wider(a: Option<VarWidth>, b: Option<VarWidth>) -> Option<VarWidth> {
67         Self::width_predicate(a, b, |a, b| a.max(b))
68     }
69
70     /// Returns the narrower of `self` and `other` (see [`Self::wider`]).
71     pub fn narrower(a: Option<VarWidth>, b: Option<VarWidth>) -> Option<VarWidth> {
72         Self::width_predicate(a, b, |a, b| a.min(b))
73     }
74
75     pub fn default_display_width(&self) -> u32 {
76         match self {
77             VarWidth::Numeric => 8,
78             VarWidth::String(width) => *width.min(&32) as u32,
79         }
80     }
81
82     pub fn from_raw(raw: impl Into<i32>) -> Result<Self, ()> {
83         let raw: i32 = raw.into();
84         match raw {
85             0 => Ok(Self::Numeric),
86             1..=255 => Ok(Self::String(raw as u16)),
87             _ => Err(()),
88         }
89     }
90 }
91
92 impl From<VarWidth> for VarType {
93     fn from(source: VarWidth) -> Self {
94         match source {
95             VarWidth::Numeric => VarType::Numeric,
96             VarWidth::String(_) => VarType::String,
97         }
98     }
99 }
100
101 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
102 pub enum Value {
103     Number(Option<OrderedFloat<f64>>),
104     String(String),
105 }
106
107 impl Value {
108     pub fn decode(raw: &raw::Value<RawStr<8>>, decoder: &Decoder) -> Self {
109         match raw {
110             raw::Value::Number(x) => Value::Number(x.map(|x| x.into())),
111             raw::Value::String(s) => Value::String(decoder.decode_exact_length(&s.0).into()),
112         }
113     }
114 }
115
116 #[derive(Clone, Debug)]
117 pub struct Dictionary {
118     pub variables: IndexSet<ByIdentifier<Variable>>,
119     pub split_file: Vec<DictIndex>,
120     pub weight: Option<DictIndex>,
121     pub filter: Option<DictIndex>,
122     pub case_limit: Option<u64>,
123     pub file_label: Option<String>,
124     pub documents: Vec<String>,
125     pub vectors: HashSet<ByIdentifier<Vector>>,
126     pub attributes: HashMap<Identifier, Vec<String>>,
127     pub mrsets: HashSet<ByIdentifier<MultipleResponseSet>>,
128     pub variable_sets: HashSet<ByIdentifier<VariableSet>>,
129     pub encoding: &'static Encoding,
130 }
131
132 #[derive(Debug)]
133 pub struct DuplicateVariableName;
134
135 impl Dictionary {
136     pub fn new(encoding: &'static Encoding) -> Self {
137         Self {
138             variables: IndexSet::new(),
139             split_file: Vec::new(),
140             weight: None,
141             filter: None,
142             case_limit: None,
143             file_label: None,
144             documents: Vec::new(),
145             vectors: HashSet::new(),
146             attributes: HashMap::new(),
147             mrsets: HashSet::new(),
148             variable_sets: HashSet::new(),
149             encoding,
150         }
151     }
152
153     pub fn add_var(&mut self, variable: Variable) -> Result<usize, DuplicateVariableName> {
154         let (index, inserted) = self.variables.insert_full(ByIdentifier::new(variable));
155         if inserted {
156             Ok(index)
157         } else {
158             Err(DuplicateVariableName)
159         }
160     }
161
162     pub fn reorder_var(&mut self, from_index: DictIndex, to_index: DictIndex) {
163         if from_index != to_index {
164             self.variables.move_index(from_index, to_index);
165             self.update_dict_indexes(&|index| {
166                 #[allow(clippy::collapsible_else_if)]
167                 if index == from_index {
168                     Some(to_index)
169                 } else if from_index < to_index {
170                     if index > from_index && index <= to_index {
171                         Some(index - 1)
172                     } else {
173                         Some(index)
174                     }
175                 } else {
176                     if index >= to_index && index < from_index {
177                         Some(index + 1)
178                     } else {
179                         Some(index)
180                     }
181                 }
182             })
183         }
184     }
185
186     pub fn retain_vars<F>(&mut self, keep: F)
187     where
188         F: Fn(&Variable) -> bool,
189     {
190         let mut deleted = Vec::new();
191         let mut index = 0;
192         self.variables.retain(|var_by_id| {
193             let keep = keep(&var_by_id.0);
194             if !keep {
195                 deleted.push(index);
196             }
197             index += 1;
198             keep
199         });
200         if !deleted.is_empty() {
201             self.update_dict_indexes(&|index| match deleted.binary_search(&index) {
202                 Ok(_) => None,
203                 Err(position) => Some(position),
204             })
205         }
206     }
207
208     pub fn delete_vars<R>(&mut self, range: R)
209     where
210         R: RangeBounds<DictIndex>,
211     {
212         let start = match range.start_bound() {
213             Bound::Included(&start) => start,
214             Bound::Excluded(&start) => start + 1,
215             Bound::Unbounded => 0,
216         };
217         let end = match range.end_bound() {
218             Bound::Included(&end) => end + 1,
219             Bound::Excluded(&end) => end,
220             Bound::Unbounded => self.variables.len(),
221         };
222         if end > start {
223             self.variables.drain(start..end);
224             self.update_dict_indexes(&|index| {
225                 if index < start {
226                     Some(index)
227                 } else if index < end {
228                     None
229                 } else {
230                     Some(index - end - start)
231                 }
232             })
233         }
234     }
235
236     fn update_dict_indexes<F>(&mut self, f: &F)
237     where
238         F: Fn(DictIndex) -> Option<DictIndex>,
239     {
240         update_dict_index_vec(&mut self.split_file, f);
241         self.weight = self.weight.and_then(f);
242         self.filter = self.filter.and_then(f);
243         self.vectors = self
244             .vectors
245             .drain()
246             .filter_map(|vector_by_id| {
247                 vector_by_id
248                     .0
249                     .with_updated_dict_indexes(f)
250                     .map(ByIdentifier::new)
251             })
252             .collect();
253         self.mrsets = self
254             .mrsets
255             .drain()
256             .filter_map(|mrset_by_id| {
257                 mrset_by_id
258                     .0
259                     .with_updated_dict_indexes(f)
260                     .map(ByIdentifier::new)
261             })
262             .collect();
263         self.variable_sets = self
264             .variable_sets
265             .drain()
266             .filter_map(|var_set_by_id| {
267                 var_set_by_id
268                     .0
269                     .with_updated_dict_indexes(f)
270                     .map(ByIdentifier::new)
271             })
272             .collect();
273     }
274 }
275
276 fn update_dict_index_vec<F>(dict_indexes: &mut Vec<DictIndex>, f: F)
277 where
278     F: Fn(DictIndex) -> Option<DictIndex>,
279 {
280     dict_indexes.retain_mut(|index| {
281         if let Some(new) = f(*index) {
282             *index = new;
283             true
284         } else {
285             false
286         }
287     });
288 }
289
290 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
291 pub enum Role {
292     Input,
293     Target,
294     Both,
295     None,
296     Partition,
297     Split,
298 }
299
300 impl Default for Role {
301     fn default() -> Self {
302         Self::Input
303     }
304 }
305
306 pub enum DictClass {
307     Ordinary,
308     System,
309     Scratch,
310 }
311
312 impl DictClass {
313     pub fn from_identifier(id: &Identifier) -> Self {
314         if id.0.starts_with('$') {
315             Self::System
316         } else if id.0.starts_with('#') {
317             Self::Scratch
318         } else {
319             Self::Ordinary
320         }
321     }
322
323     pub fn must_leave(self) -> bool {
324         match self {
325             DictClass::Ordinary => false,
326             DictClass::System => false,
327             DictClass::Scratch => true,
328         }
329     }
330 }
331
332 #[derive(Clone, Debug)]
333 pub struct Variable {
334     pub name: Identifier,
335     pub width: VarWidth,
336     pub missing_values: MissingValues,
337     pub print_format: Spec,
338     pub write_format: Spec,
339     pub value_labels: HashMap<Value, String>,
340     pub label: Option<String>,
341     pub measure: Option<Measure>,
342     pub role: Role,
343     pub display_width: u32,
344     pub alignment: Alignment,
345     pub leave: bool,
346     pub short_names: Vec<Identifier>,
347     pub attributes: HashSet<ByIdentifier<Attribute>>,
348 }
349
350 impl Variable {
351     pub fn new(name: Identifier, width: VarWidth) -> Self {
352         let var_type = VarType::from_width(width);
353         let leave = DictClass::from_identifier(&name).must_leave();
354         Self {
355             name,
356             width,
357             missing_values: MissingValues::default(),
358             print_format: Spec::default_for_width(width),
359             write_format: Spec::default_for_width(width),
360             value_labels: HashMap::new(),
361             label: None,
362             measure: Measure::default_for_type(var_type),
363             role: Role::default(),
364             display_width: width.default_display_width(),
365             alignment: Alignment::default_for_type(var_type),
366             leave,
367             short_names: Vec::new(),
368             attributes: HashSet::new(),
369         }
370     }
371 }
372
373 impl HasIdentifier for Variable {
374     fn identifier(&self) -> &Identifier {
375         &self.name
376     }
377 }
378
379 #[derive(Clone, Debug)]
380 pub struct Vector {
381     pub name: Identifier,
382     pub variables: Vec<DictIndex>,
383 }
384
385 impl Vector {
386     fn with_updated_dict_indexes(
387         mut self,
388         f: impl Fn(DictIndex) -> Option<DictIndex>,
389     ) -> Option<Self> {
390         update_dict_index_vec(&mut self.variables, f);
391         (!self.variables.is_empty()).then_some(self)
392     }
393 }
394
395 impl HasIdentifier for Vector {
396     fn identifier(&self) -> &Identifier {
397         &self.name
398     }
399 }
400
401 #[derive(Clone, Debug)]
402 pub struct Attribute {
403     pub name: Identifier,
404     pub values: Vec<String>,
405 }
406
407 impl HasIdentifier for Attribute {
408     fn identifier(&self) -> &Identifier {
409         &self.name
410     }
411 }
412
413 #[derive(Clone, Debug)]
414 pub struct MultipleResponseSet {
415     pub name: Identifier,
416     pub label: String,
417     pub mr_type: MultipleResponseType,
418     pub variables: Vec<DictIndex>,
419 }
420
421 impl MultipleResponseSet {
422     fn with_updated_dict_indexes(
423         mut self,
424         f: impl Fn(DictIndex) -> Option<DictIndex>,
425     ) -> Option<Self> {
426         update_dict_index_vec(&mut self.variables, f);
427         (self.variables.len() > 1).then_some(self)
428     }
429 }
430
431 impl HasIdentifier for MultipleResponseSet {
432     fn identifier(&self) -> &Identifier {
433         &self.name
434     }
435 }
436
437 #[derive(Clone, Debug)]
438 pub enum MultipleResponseType {
439     MultipleDichotomy {
440         value: Value,
441         labels: CategoryLabels,
442     },
443     MultipleCategory,
444 }
445
446 #[derive(Clone, Debug)]
447 pub struct VariableSet {
448     pub name: Identifier,
449     pub variables: Vec<DictIndex>,
450 }
451
452 impl VariableSet {
453     fn with_updated_dict_indexes(
454         mut self,
455         f: impl Fn(DictIndex) -> Option<DictIndex>,
456     ) -> Option<Self> {
457         update_dict_index_vec(&mut self.variables, f);
458         (!self.variables.is_empty()).then_some(self)
459     }
460 }
461
462 impl HasIdentifier for VariableSet {
463     fn identifier(&self) -> &Identifier {
464         &self.name
465     }
466 }
467
468 #[cfg(test)]
469 mod test {
470     use std::collections::HashSet;
471
472     use crate::identifier::Identifier;
473
474     use super::{ByIdentifier, HasIdentifier};
475
476     #[derive(PartialEq, Eq, Debug, Clone)]
477     struct Variable {
478         name: Identifier,
479         value: i32,
480     }
481
482     impl HasIdentifier for Variable {
483         fn identifier(&self) -> &Identifier {
484             &self.name
485         }
486     }
487
488     #[test]
489     fn test() {
490         // Variables should not be the same if their values differ.
491         let abcd = Identifier::new_utf8("abcd").unwrap();
492         let abcd1 = Variable {
493             name: abcd.clone(),
494             value: 1,
495         };
496         let abcd2 = Variable {
497             name: abcd,
498             value: 2,
499         };
500         assert_ne!(abcd1, abcd2);
501
502         // But `ByName` should treat them the same.
503         let abcd1_by_name = ByIdentifier::new(abcd1);
504         let abcd2_by_name = ByIdentifier::new(abcd2);
505         assert_eq!(abcd1_by_name, abcd2_by_name);
506
507         // And a `HashSet` of `ByName` should also treat them the same.
508         let mut vars: HashSet<ByIdentifier<Variable>> = HashSet::new();
509         assert!(vars.insert(ByIdentifier::new(abcd1_by_name.0.clone())));
510         assert!(!vars.insert(ByIdentifier::new(abcd2_by_name.0.clone())));
511         assert_eq!(
512             vars.get(&Identifier::new_utf8("abcd").unwrap())
513                 .unwrap()
514                 .0
515                 .value,
516             1
517         );
518     }
519 }