mod build;
mod filter;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::filter::FilterOp;
use crate::sort::SortDirection;
use crate::value::Value;
pub(crate) 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::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::filter_int(col, op, value),
Column::Float(col) => filter::filter_float(col, op, value),
Column::Str(col) => filter::filter_str(col, op, value),
Column::Bool(col) => filter::filter_bool(col, op, value),
}
}
#[must_use]
pub fn sort_subset(
&self,
order: &[usize],
keys: &[(&str, SortDirection)],
limit: Option<usize>,
) -> 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();
if let [(column, desc)] = resolved.as_slice() {
sort_one_key(&mut order, column, *desc, limit);
} else {
partial_sort(&mut order, limit, |&a, &b| {
for (column, desc) in &resolved {
let ord = oriented(column.compare(a, b), *desc);
if ord != Ordering::Equal {
return ord;
}
}
a.cmp(&b)
});
}
Some(order)
}
}
impl Column {
fn compare(&self, a: usize, b: usize) -> Ordering {
match self {
Column::Int(col) => col[a].cmp(&col[b]),
Column::Float(col) => col[a].partial_cmp(&col[b]).unwrap_or(Ordering::Equal),
Column::Str(col) => col[a].cmp(&col[b]),
Column::Bool(col) => col[a].cmp(&col[b]),
}
}
}
fn sort_one_key(order: &mut Vec<usize>, column: &Column, desc: bool, limit: Option<usize>) {
match column {
Column::Int(c) => sort_by_key(order, c, desc, limit),
Column::Str(c) => sort_by_key(order, c, desc, limit),
Column::Bool(c) => sort_by_key(order, c, desc, limit),
Column::Float(c) => partial_sort(order, limit, |&a, &b| {
oriented(c[a].partial_cmp(&c[b]).unwrap_or(Ordering::Equal), desc)
.then_with(|| a.cmp(&b))
}),
}
}
fn sort_by_key<T: Ord>(order: &mut Vec<usize>, col: &[T], desc: bool, limit: Option<usize>) {
partial_sort(order, limit, |&a, &b| {
oriented(col[a].cmp(&col[b]), desc).then_with(|| a.cmp(&b))
});
}
fn partial_sort(
order: &mut Vec<usize>,
limit: Option<usize>,
mut cmp: impl FnMut(&usize, &usize) -> Ordering,
) {
match limit {
Some(0) => order.clear(),
Some(k) if k < order.len() => {
order.select_nth_unstable_by(k - 1, &mut cmp);
order.truncate(k);
order.sort_unstable_by(&mut cmp);
}
_ => order.sort_unstable_by(&mut cmp),
}
}
fn oriented(ordering: Ordering, desc: bool) -> Ordering {
if desc {
ordering.reverse()
} else {
ordering
}
}
#[cfg(test)]
mod tests;