use std::collections::HashMap;
use std::collections::hash_map::Entry;
use crate::data::IntoSeries;
#[derive(Debug, Clone)]
pub struct Agg {
keys: HashMap<String, usize>,
groups: Vec<Vec<f64>>,
}
impl Agg {
pub fn reduce(self, reducer: super::Reducer) -> (Vec<String>, Vec<f64>) {
let mut ordered_keys = vec![None; self.groups.len()];
for (key, index) in self.keys {
ordered_keys[index] = Some(key);
}
let keys = ordered_keys
.into_iter()
.map(|key| key.expect("every group has one interned key"))
.collect();
let values = self
.groups
.iter()
.map(|group| reducer.reduce(group))
.collect();
(keys, values)
}
pub fn by<'a>(
keys: impl IntoIterator<Item = impl Into<String>>,
values: impl IntoSeries<'a>,
) -> Agg {
let values = values.into_series();
let mut keys = keys.into_iter();
let mut result = Agg {
keys: HashMap::new(),
groups: Vec::new(),
};
for value in values.iter() {
let Some(key) = keys.next() else {
panic!("Agg::by requires one key per value");
};
let index = match result.keys.entry(key.into()) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let index = result.groups.len();
entry.insert(index);
result.groups.push(Vec::new());
index
}
};
if value.is_finite() {
result.groups[index].push(value);
}
}
assert!(keys.next().is_none(), "Agg::by requires one key per value");
result
}
pub fn count(self) -> (Vec<String>, Vec<f64>) {
self.reduce(super::Reducer::Count)
}
pub fn sum(self) -> (Vec<String>, Vec<f64>) {
self.reduce(super::Reducer::Sum)
}
pub fn mean(self) -> (Vec<String>, Vec<f64>) {
self.reduce(super::Reducer::Mean)
}
pub fn min(self) -> (Vec<String>, Vec<f64>) {
self.reduce(super::Reducer::Min)
}
pub fn max(self) -> (Vec<String>, Vec<f64>) {
self.reduce(super::Reducer::Max)
}
pub fn median(self) -> (Vec<String>, Vec<f64>) {
self.reduce(super::Reducer::Median)
}
}
#[cfg(test)]
#[path = "tests/agg_tests.rs"]
mod tests;