use std::collections::HashSet;
use crate::error::MattenDataError;
use crate::schema::SchemaSummary;
use crate::{numeric, schema};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum CellValue {
Text(String),
Float(f64),
Int(i64),
Bool(bool),
Missing,
}
impl From<f64> for CellValue {
fn from(v: f64) -> Self {
CellValue::Float(v)
}
}
impl From<i64> for CellValue {
fn from(v: i64) -> Self {
CellValue::Int(v)
}
}
impl From<bool> for CellValue {
fn from(v: bool) -> Self {
CellValue::Bool(v)
}
}
impl From<&str> for CellValue {
fn from(v: &str) -> Self {
CellValue::Text(v.to_string())
}
}
impl From<String> for CellValue {
fn from(v: String) -> Self {
CellValue::Text(v)
}
}
#[derive(Debug, Clone)]
pub struct Table {
headers: Vec<String>,
rows: Vec<Vec<CellValue>>,
}
impl Table {
pub(crate) fn from_parts(headers: Vec<String>, rows: Vec<Vec<CellValue>>) -> Self {
Table { headers, rows }
}
pub fn row_count(&self) -> usize {
self.rows.len()
}
pub fn column_count(&self) -> usize {
self.headers.len()
}
pub fn column_names(&self) -> &[String] {
&self.headers
}
pub fn schema_summary(&self) -> SchemaSummary {
schema::summarize(self)
}
pub fn select_columns<I, S>(&self, columns: I) -> Result<Table, MattenDataError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let requested: Vec<String> = columns
.into_iter()
.map(|s| s.as_ref().to_string())
.collect();
if requested.is_empty() {
return Err(MattenDataError::EmptySelection);
}
let mut seen = HashSet::with_capacity(requested.len());
for name in &requested {
if !seen.insert(name.as_str()) {
return Err(MattenDataError::DuplicateSelection { name: name.clone() });
}
}
let mut indices = Vec::with_capacity(requested.len());
for name in &requested {
match self.headers.iter().position(|h| h == name) {
Some(idx) => indices.push(idx),
None => return Err(MattenDataError::MissingColumn { name: name.clone() }),
}
}
let rows = self
.rows
.iter()
.map(|row| indices.iter().map(|&i| row[i].clone()).collect())
.collect();
Ok(Table::from_parts(requested, rows))
}
pub fn fill_missing(&self, value: impl Into<CellValue>) -> Result<Table, MattenDataError> {
let fill = value.into();
let rows = self
.rows
.iter()
.map(|row| {
row.iter()
.map(|cell| {
if matches!(cell, CellValue::Missing) {
fill.clone()
} else {
cell.clone()
}
})
.collect()
})
.collect();
Ok(Table::from_parts(self.headers.clone(), rows))
}
pub fn try_numeric(&self) -> Result<numeric::NumericTable, MattenDataError> {
numeric::try_numeric(self)
}
pub(crate) fn headers(&self) -> &[String] {
&self.headers
}
pub(crate) fn rows(&self) -> &[Vec<CellValue>] {
&self.rows
}
}