use std::cmp::Ordering;
use crate::column::{BooleanColumn, Column, Float64Column, Int64Column, StringColumn};
use crate::error::{Error, Result};
use crate::optimized::split_dataframe::core::OptimizedDataFrame;
#[inline]
fn order_with_na<T, C>(a: Option<&T>, b: Option<&T>, ascending: bool, cmp: &C) -> Ordering
where
C: Fn(&T, &T) -> Ordering,
{
match (a, b) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(x), Some(y)) => {
let ordering = cmp(x, y);
if ascending {
ordering
} else {
ordering.reverse()
}
}
}
}
#[inline]
fn total_cmp_f64(a: &f64, b: &f64) -> Ordering {
a.total_cmp(b)
}
#[inline]
fn float_sort_key(col: &Float64Column, row: usize) -> Option<f64> {
col.get(row).ok().flatten().filter(|value| !value.is_nan())
}
fn sorted_indices<T, K, C>(row_count: usize, ascending: bool, key: K, cmp: C) -> Vec<usize>
where
K: Fn(usize) -> Option<T>,
C: Fn(&T, &T) -> Ordering,
{
let mut pairs: Vec<(usize, Option<T>)> = (0..row_count).map(|idx| (idx, key(idx))).collect();
pairs.sort_by(|a, b| order_with_na(a.1.as_ref(), b.1.as_ref(), ascending, &cmp));
pairs.into_iter().map(|(idx, _)| idx).collect()
}
enum SortColumnRef<'a> {
Int64(&'a Int64Column),
Float64(&'a Float64Column),
String(&'a StringColumn),
Boolean(&'a BooleanColumn),
}
impl<'a> SortColumnRef<'a> {
fn new(column: &'a Column) -> Self {
match column {
Column::Int64(col) => SortColumnRef::Int64(col),
Column::Float64(col) => SortColumnRef::Float64(col),
Column::String(col) => SortColumnRef::String(col),
Column::Boolean(col) => SortColumnRef::Boolean(col),
}
}
#[inline]
fn compare_rows(&self, a: usize, b: usize, ascending: bool) -> Ordering {
match self {
SortColumnRef::Int64(col) => order_with_na(
col.get(a).ok().flatten().as_ref(),
col.get(b).ok().flatten().as_ref(),
ascending,
&i64::cmp,
),
SortColumnRef::Float64(col) => order_with_na(
float_sort_key(col, a).as_ref(),
float_sort_key(col, b).as_ref(),
ascending,
&total_cmp_f64,
),
SortColumnRef::String(col) => order_with_na(
col.get(a).ok().flatten().as_ref(),
col.get(b).ok().flatten().as_ref(),
ascending,
&|x: &&str, y: &&str| x.cmp(y),
),
SortColumnRef::Boolean(col) => order_with_na(
col.get(a).ok().flatten().as_ref(),
col.get(b).ok().flatten().as_ref(),
ascending,
&bool::cmp,
),
}
}
}
impl OptimizedDataFrame {
pub fn sort_by(&self, by: &str, ascending: bool) -> Result<Self> {
let column_idx = *self
.column_indices
.get(by)
.ok_or_else(|| Error::ColumnNotFound(by.to_string()))?;
let column = self
.columns
.get(column_idx)
.ok_or_else(|| Error::ColumnNotFound(by.to_string()))?;
let row_count = self.row_count();
let indices = match column {
Column::Int64(col) => sorted_indices(
row_count,
ascending,
|idx| col.get(idx).ok().flatten(),
i64::cmp,
),
Column::Float64(col) => sorted_indices(
row_count,
ascending,
|idx| float_sort_key(col, idx),
total_cmp_f64,
),
Column::String(col) => sorted_indices(
row_count,
ascending,
|idx| col.get(idx).ok().flatten(),
|x: &&str, y: &&str| x.cmp(y),
),
Column::Boolean(col) => sorted_indices(
row_count,
ascending,
|idx| col.get(idx).ok().flatten(),
bool::cmp,
),
};
self.select_rows_by_indices_internal(&indices)
}
pub fn sort_by_columns(&self, by: &[&str], ascending: Option<&[bool]>) -> Result<Self> {
if by.is_empty() {
return Err(Error::EmptyColumnList);
}
let is_ascending: Vec<bool> = match ascending {
Some(asc) => {
if asc.len() != by.len() {
return Err(Error::InconsistentArrayLengths {
expected: by.len(),
found: asc.len(),
});
}
asc.to_vec()
}
None => vec![true; by.len()], };
let mut sort_columns: Vec<(SortColumnRef<'_>, bool)> = Vec::with_capacity(by.len());
for (&col_name, &asc) in by.iter().zip(is_ascending.iter()) {
let column_idx = *self
.column_indices
.get(col_name)
.ok_or_else(|| Error::ColumnNotFound(col_name.to_string()))?;
let column = self
.columns
.get(column_idx)
.ok_or_else(|| Error::ColumnNotFound(col_name.to_string()))?;
sort_columns.push((SortColumnRef::new(column), asc));
}
let mut indices: Vec<usize> = (0..self.row_count()).collect();
indices.sort_by(|&a, &b| {
for (column, asc) in &sort_columns {
let cmp = column.compare_rows(a, b, *asc);
if cmp != Ordering::Equal {
return cmp;
}
}
Ordering::Equal
});
self.select_rows_by_indices_internal(&indices)
}
fn select_rows_by_indices_internal(&self, indices: &[usize]) -> Result<Self> {
use crate::optimized::split_dataframe::select;
select::select_rows_by_indices_impl(self, indices)
}
}