rustronomy_core/universal_containers/
table.rs

1/*
2  Copyright© 2022 Raúl Wolters(1)
3
4  This file is part of rustronomy-core.
5
6  rustronomy is free software: you can redistribute it and/or modify it under
7  the terms of the European Union Public License version 1.2 or later, as
8  published by the European Commission.
9
10  rustronomy is distributed in the hope that it will be useful, but WITHOUT ANY
11  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12  A PARTICULAR PURPOSE. See the European Union Public License for more details.
13
14  You should have received a copy of the EUPL in an/all official language(s) of
15  the European Union along with rustronomy.  If not, see
16  <https://ec.europa.eu/info/european-union-public-licence_en/>.
17
18  (1) Resident of the Kingdom of the Netherlands; agreement between licensor and
19  licensee subject to Dutch law as per article 15 of the EUPL.
20*/
21
22//! This module specifies a table container. Tables consist of named columns
23//! that hold a bunch of values of the same type. Tables should not be used to
24//! store large amounts of data.
25//!
26//! Three column types are supported:
27//! - `Integer` always a `Vec<i64>`
28//! - `Float` always a `Vec<f64>`
29//! - `Text` always a `Vec<String>`
30//!
31//! Columns can be accessed either through their index or through their name, if
32//! one has been supplied.
33
34use std::{
35  fmt::{self, Debug, Display, Formatter},
36  mem,
37};
38
39use indexmap::IndexMap;
40
41#[derive(Debug, Clone)]
42#[non_exhaustive]
43/// columns are the constituent parts of tables. They consist of vectors holding
44/// elements of the same type. Types are differentiated via the variants of this
45/// enum.
46///
47/// Right now, 3 variants are supported:
48/// - `Integer` always a `Vec<i64>`
49/// - `Float` always a `Vec<f64>`
50/// - `Text` always a `Vec<String>`
51/// In the future, more variants may be added as necessary. As such, this enum
52/// is marked as `#[non_exhaustive]`.
53pub enum Col {
54  Integer(Vec<i64>),
55  Float(Vec<f64>),
56  Text(Vec<String>),
57}
58
59impl Col {
60  #[inline]
61  /// returns the number of elements in the column
62  pub fn len(&self) -> usize {
63    use Col::*;
64    match self {
65      Integer(vec) => vec.len(),
66      Float(vec) => vec.len(),
67      Text(vec) => vec.len(),
68    }
69  }
70
71  /// returns the total size of the column in bytes
72  pub fn size(&self) -> usize {
73    //total size = discriminant + usize + the size of the underlying vector
74    mem::size_of::<Self>()
75      + self.len()
76        * match self {
77          Col::Integer(_) => mem::size_of::<i64>(),
78          Col::Float(_) => mem::size_of::<f64>(),
79          Col::Text(vec) => vec.iter().map(|string| string.bytes().len()).sum(),
80        }
81  }
82}
83
84#[derive(Debug, Clone)]
85/// the table data container. Consists of named columns and metadata tags. See
86/// the module-level documentation for more details.
87pub struct Table {
88  //Indexmap to provide easy iteration
89  data: IndexMap<String, Col>,
90}
91
92impl Table {
93  /// creates an empty table without metadata
94  pub fn new() -> Table {
95    Table { data: IndexMap::new() }
96  }
97
98  #[inline]
99  /// adds column to the table and gives it a name, if specified. If a column
100  /// with the same name already exists, it will be overridden.
101  pub fn set_col(&mut self, col_name: &str, col: Col) {
102    self.data.insert(col_name.to_string(), col);
103  }
104
105  #[inline]
106  /// returns reference to the column with the name `col_name` if one exists,
107  /// `None` otherwise.
108  pub fn get_col(&self, col_name: &str) -> Option<&Col> {
109    self.data.get(col_name)
110  }
111
112  #[inline]
113  /// returns mutable reference to the column with the name `col_name` if one
114  /// exists, `None` otherwise.
115  pub fn get_col_mut(&mut self, col_name: &str) -> Option<&mut Col> {
116    self.data.get_mut(col_name)
117  }
118
119  #[inline]
120  /// removes column with the name `col_name` if one exists and returns it.
121  /// Returns `None` if no column named `col_name` exists.
122  pub fn remove_col(&mut self, col_name: &str) -> Option<Col> {
123    self.data.remove(col_name)
124  }
125
126  #[inline]
127  /// returns vec of (column-name, column) pairs, discarding the metadata tags
128  pub fn data(self) -> Vec<(String, Col)> {
129    self.data.into_iter().collect()
130  }
131
132  #[inline]
133  /// returns vec of columns discarding column names and metadata tags
134  pub fn data_unnamed(self) -> Vec<Col> {
135    self.data.into_values().collect()
136  }
137}
138
139impl Display for Table {
140  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141    #[cfg_attr(rustfmt, rustfmt_skip)]
142    writeln!(f, ">=============================<|RUSTRONOMY TABLE 🦀🌌|>===========================<")?;
143    writeln!(f, "number of colums: {}", self.data.len())?;
144    write!(f, "shape: (")?;
145    for (_name, val) in self.data.iter() {
146      write!(f, "{},", val.len())?;
147    }
148    write!(f, "\u{0008})\n")?;
149    write!(
150      f,
151      "total size: {}",
152      super::fmt_byte_size(self.data.iter().map(|(_name, col)| col.size()).sum())
153    )?;
154    #[cfg_attr(rustfmt, rustfmt_skip)]
155    writeln!(f, ">-----------------------------------<|COLUMNS|>---------------------------------<")?;
156    for (col_name, col) in self.data.iter() {
157      writeln!(f, "[{}]", col_name)?;
158      writeln!(f, "    number of elements: {}", col.len())?;
159      writeln!(f, "    size: {}", super::fmt_byte_size(col.size()))?;
160    }
161    #[cfg_attr(rustfmt, rustfmt_skip)]
162    writeln!(f, ">==============================================================================<")
163  }
164}