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