use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::coerce;
use crate::filter::FilterOp;
use crate::sort::SortDirection;
use crate::value::Value;
enum Column {
Int(Vec<i64>),
Float(Vec<f64>),
Str(Vec<String>),
Bool(Vec<bool>),
}
pub struct Columns {
columns: BTreeMap<String, Column>,
}
impl Columns {
#[must_use]
pub fn build(items: &[Value]) -> Self {
let mut columns = BTreeMap::new();
let Some(Value::Map(first)) = items.first() else {
return Self { columns };
};
for (field, seed) in first {
if let Some(column) = build_column(items, field, seed) {
columns.insert(field.clone(), column);
}
}
Self { columns }
}
#[must_use]
pub fn filter(&self, field: &str, op: FilterOp, value: &Value) -> Option<Vec<usize>> {
match self.columns.get(field)? {
Column::Int(col) => filter_int(col, op, value),
Column::Float(col) => filter_float(col, op, value),
Column::Str(col) => filter_str(col, op, value),
Column::Bool(col) => filter_bool(col, op, value),
}
}
#[must_use]
pub fn sort_subset(
&self,
order: &[usize],
keys: &[(&str, SortDirection)],
) -> Option<Vec<usize>> {
if keys.is_empty() {
return None;
}
let resolved: Vec<(&Column, bool)> = keys
.iter()
.map(|(field, direction)| {
self.columns
.get(*field)
.map(|column| (column, *direction == SortDirection::Desc))
})
.collect::<Option<Vec<_>>>()?;
let mut order = order.to_vec();
for (column, desc) in resolved.iter().rev() {
sort_one(&mut order, column, *desc);
}
Some(order)
}
}
fn sort_one(order: &mut [usize], column: &Column, desc: bool) {
match column {
Column::Int(col) => order.sort_by(|&a, &b| oriented(col[a].cmp(&col[b]), desc)),
Column::Float(col) => order.sort_by(|&a, &b| {
oriented(col[a].partial_cmp(&col[b]).unwrap_or(Ordering::Equal), desc)
}),
Column::Str(col) => order.sort_by(|&a, &b| oriented(col[a].cmp(&col[b]), desc)),
Column::Bool(col) => order.sort_by(|&a, &b| oriented(col[a].cmp(&col[b]), desc)),
}
}
fn oriented(ordering: Ordering, desc: bool) -> Ordering {
if desc {
ordering.reverse()
} else {
ordering
}
}
fn build_column(items: &[Value], field: &str, seed: &Value) -> Option<Column> {
match seed {
Value::Int(_) => build_int(items, field),
Value::Float(f) if !f.is_nan() => build_float(items, field),
Value::Str(_) => build_str(items, field),
Value::Bool(_) => build_bool(items, field),
_ => None,
}
}
fn field_value<'a>(item: &'a Value, field: &str) -> Option<&'a Value> {
match item {
Value::Map(map) => map.get(field),
_ => None,
}
}
fn build_int(items: &[Value], field: &str) -> Option<Column> {
let mut col = Vec::with_capacity(items.len());
for item in items {
match field_value(item, field)? {
Value::Int(n) => col.push(*n),
_ => return None, }
}
Some(Column::Int(col))
}
fn build_float(items: &[Value], field: &str) -> Option<Column> {
let mut col = Vec::with_capacity(items.len());
for item in items {
match field_value(item, field)? {
Value::Float(f) if !f.is_nan() => col.push(*f),
_ => return None, }
}
Some(Column::Float(col))
}
fn build_str(items: &[Value], field: &str) -> Option<Column> {
let mut col = Vec::with_capacity(items.len());
for item in items {
match field_value(item, field)? {
Value::Str(s) => col.push(s.clone()),
_ => return None, }
}
Some(Column::Str(col))
}
fn build_bool(items: &[Value], field: &str) -> Option<Column> {
let mut col = Vec::with_capacity(items.len());
for item in items {
match field_value(item, field)? {
Value::Bool(b) => col.push(*b),
_ => return None, }
}
Some(Column::Bool(col))
}
fn collect(len: usize, keep: impl Fn(usize) -> bool) -> Vec<usize> {
(0..len).filter(|&i| keep(i)).collect()
}
fn filter_int(col: &[i64], op: FilterOp, value: &Value) -> Option<Vec<usize>> {
let &Value::Int(needle) = value else {
return None; };
let keep: fn(&i64, &i64) -> bool = comparison(op)?;
Some(collect(col.len(), |i| keep(&col[i], &needle)))
}
fn filter_float(col: &[f64], op: FilterOp, value: &Value) -> Option<Vec<usize>> {
let needle = coerce::as_number(value)?; if needle.is_nan() {
return None; }
let keep: fn(&f64, &f64) -> bool = comparison(op)?;
Some(collect(col.len(), |i| keep(&col[i], &needle)))
}
fn filter_str(col: &[String], op: FilterOp, value: &Value) -> Option<Vec<usize>> {
let Value::Str(needle) = value else {
return None; };
let keep: fn(&str, &str) -> bool = comparison(op)?;
Some(collect(col.len(), |i| {
keep(col[i].as_str(), needle.as_str())
}))
}
fn filter_bool(col: &[bool], op: FilterOp, value: &Value) -> Option<Vec<usize>> {
let &Value::Bool(needle) = value else {
return None; };
let keep: fn(&bool, &bool) -> bool = comparison(op)?;
Some(collect(col.len(), |i| keep(&col[i], &needle)))
}
fn comparison<T: PartialOrd + ?Sized>(op: FilterOp) -> Option<fn(&T, &T) -> bool> {
Some(match op {
FilterOp::Gt => |a, b| a > b,
FilterOp::Gte => |a, b| a >= b,
FilterOp::Lt => |a, b| a < b,
FilterOp::Lte => |a, b| a <= b,
FilterOp::Eq => |a, b| a == b,
FilterOp::Ne => |a, b| a != b,
_ => return None,
})
}
#[cfg(test)]
mod tests;