use std::cmp::Ordering;
use std::collections::HashMap;
use crate::buffer::Buffer;
use crate::strbuf::StrBuffer;
use crate::datetime;
use crate::dtype::DType;
use crate::error::{Result, VolasError};
use crate::numeric::{binary_supertype, fits, Numeric};
use crate::stats;
use crate::validity::Validity;
macro_rules! numeric_dispatch {
($col:expr, $slice:ident => $body:expr) => {
match $col {
Column::F64(buf) => {
let $slice: &[f64] = buf.as_slice();
Ok($body)
}
Column::F32(buf) => {
let $slice: &[f32] = buf.as_slice();
Ok($body)
}
Column::I64(buf, _) => {
let $slice: &[i64] = buf.as_slice();
Ok($body)
}
Column::I32(buf, _) => {
let $slice: &[i32] = buf.as_slice();
Ok($body)
}
other => Err(VolasError::DType(format!(
"expected a numeric column, got {}",
other.dtype()
))),
}
};
}
#[derive(Clone, Debug, PartialEq)]
pub enum Column {
F64(Buffer<f64>),
F32(Buffer<f32>),
Bool(Buffer<bool>, Validity),
I64(Buffer<i64>, Validity),
I32(Buffer<i32>, Validity),
Str(StrBuffer, Validity),
Datetime(Buffer<i64>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CombineOp {
Keep,
Replace,
Max,
Min,
Sum,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Scalar {
F64(f64),
F32(f32),
I64(i64),
I32(i32),
Bool(bool),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinOp {
Add,
Sub,
Mul,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BoolOp {
And,
Or,
Xor,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CmpOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
impl CmpOp {
fn matches(self, o: Ordering) -> bool {
match self {
CmpOp::Eq => o == Ordering::Equal,
CmpOp::Ne => o != Ordering::Equal,
CmpOp::Lt => o == Ordering::Less,
CmpOp::Le => o != Ordering::Greater,
CmpOp::Gt => o == Ordering::Greater,
CmpOp::Ge => o != Ordering::Less,
}
}
fn matches_f64(self, a: f64, b: f64) -> bool {
match self {
CmpOp::Eq => a == b,
CmpOp::Ne => a != b,
CmpOp::Lt => a < b,
CmpOp::Le => a <= b,
CmpOp::Gt => a > b,
CmpOp::Ge => a >= b,
}
}
}
mod cast;
mod ops;
mod order;
mod transform;
impl Column {
pub fn f64(v: Vec<f64>) -> Column {
Column::F64(Buffer::from_vec(v))
}
pub fn f32(v: Vec<f32>) -> Column {
Column::F32(Buffer::from_vec(v))
}
pub fn i32(v: Vec<i32>) -> Column {
Column::I32(Buffer::from_vec(v), Validity::dense())
}
pub fn bool(v: Vec<bool>) -> Column {
Column::Bool(Buffer::from_vec(v), Validity::dense())
}
pub fn i64(v: Vec<i64>) -> Column {
Column::I64(Buffer::from_vec(v), Validity::dense())
}
pub fn i64_with(v: Vec<i64>, validity: Validity) -> Column {
Column::I64(Buffer::from_vec(v), validity)
}
pub fn i32_with(v: Vec<i32>, validity: Validity) -> Column {
Column::I32(Buffer::from_vec(v), validity)
}
pub fn bool_with(v: Vec<bool>, validity: Validity) -> Column {
Column::Bool(Buffer::from_vec(v), validity)
}
pub fn str(v: Vec<String>) -> Column {
Column::Str(StrBuffer::from_vec(v), Validity::dense())
}
pub fn str_with(v: Vec<String>, validity: Validity) -> Column {
Column::Str(StrBuffer::from_vec(v), validity)
}
pub fn datetime(v: Vec<i64>) -> Column {
Column::Datetime(Buffer::from_vec(v))
}
pub fn na_of(dtype: DType, len: usize) -> Column {
let all_na = || Validity::from_valid_iter(len, std::iter::repeat_n(false, len));
match dtype {
DType::F64 => Column::f64(vec![f64::NAN; len]),
DType::F32 => Column::f32(vec![f32::NAN; len]),
DType::I64 => Column::i64_with(vec![0; len], all_na()),
DType::I32 => Column::i32_with(vec![0; len], all_na()),
DType::Bool => Column::bool_with(vec![false; len], all_na()),
DType::Utf8 => Column::str_with(vec![String::new(); len], all_na()),
DType::Datetime => Column::datetime(vec![i64::MIN; len]),
}
}
pub fn len(&self) -> usize {
match self {
Column::F64(v) => v.len(),
Column::F32(v) => v.len(),
Column::Bool(v, _) => v.len(),
Column::I64(v, _) => v.len(),
Column::I32(v, _) => v.len(),
Column::Str(v, _) => v.len(),
Column::Datetime(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn dtype(&self) -> DType {
match self {
Column::F64(_) => DType::F64,
Column::F32(_) => DType::F32,
Column::Bool(_, _) => DType::Bool,
Column::I64(_, _) => DType::I64,
Column::I32(_, _) => DType::I32,
Column::Str(_, _) => DType::Utf8,
Column::Datetime(_) => DType::Datetime,
}
}
pub fn is_valid(&self, i: usize) -> bool {
match self {
Column::F64(v) => !v[i].is_nan(),
Column::F32(v) => !v[i].is_nan(),
Column::Bool(_, val)
| Column::I64(_, val)
| Column::I32(_, val)
| Column::Str(_, val) => val.is_valid(i),
Column::Datetime(v) => v[i] != i64::MIN,
}
}
pub fn get_i64(&self, i: usize) -> i64 {
match self {
Column::I64(v, _) => v[i],
Column::I32(v, _) => v[i] as i64,
Column::Datetime(v) => v[i],
Column::F64(v) => v[i] as i64,
Column::F32(v) => v[i] as i64,
Column::Bool(v, _) => v[i] as i64,
Column::Str(_, _) => 0,
}
}
pub fn null_count(&self) -> usize {
match self {
Column::F64(v) => v.iter().filter(|x| x.is_nan()).count(),
Column::F32(v) => v.iter().filter(|x| x.is_nan()).count(),
Column::Bool(_, val)
| Column::I64(_, val)
| Column::I32(_, val)
| Column::Str(_, val) => val.null_count(),
Column::Datetime(v) => v.iter().filter(|&&x| x == i64::MIN).count(),
}
}
fn with_validity(self, validity: Validity) -> Column {
match self {
Column::I64(v, _) => Column::I64(v, validity),
Column::I32(v, _) => Column::I32(v, validity),
other => other,
}
}
fn validity(&self) -> Option<&Validity> {
match self {
Column::Bool(_, val)
| Column::I64(_, val)
| Column::I32(_, val)
| Column::Str(_, val) => Some(val),
_ => None,
}
}
pub fn as_f64(&self) -> Option<&[f64]> {
if let Column::F64(v) = self {
Some(v.as_slice())
} else {
None
}
}
pub fn as_bool(&self) -> Option<&[bool]> {
if let Column::Bool(v, _) = self {
Some(v.as_slice())
} else {
None
}
}
pub fn as_i64(&self) -> Option<&[i64]> {
if let Column::I64(v, _) = self {
Some(v.as_slice())
} else {
None
}
}
pub fn str_at(&self, i: usize) -> Option<&str> {
if let Column::Str(v, _) = self {
Some(v.get(i))
} else {
None
}
}
pub fn as_datetime(&self) -> Option<&[i64]> {
if let Column::Datetime(v) = self {
Some(v.as_slice())
} else {
None
}
}
pub fn to_f64_vec(&self) -> Vec<f64> {
match self {
Column::F64(v) => v.to_vec(),
Column::F32(v) => v.iter().map(|&x| x as f64).collect(),
Column::Bool(v, val) => mask_f64(v.iter().map(|&b| if b { 1.0 } else { 0.0 }), val),
Column::I64(v, val) => mask_f64(v.iter().map(|&i| i as f64), val),
Column::I32(v, val) => mask_f64(v.iter().map(|&i| i as f64), val),
Column::Str(v, _) => vec![f64::NAN; v.len()],
Column::Datetime(v) => v
.iter()
.map(|&i| if i == i64::MIN { f64::NAN } else { i as f64 })
.collect(),
}
}
pub fn get_f64(&self, i: usize) -> f64 {
match self {
Column::F64(v) => v[i],
Column::F32(v) => v[i] as f64,
Column::Bool(v, _) => {
if v[i] {
1.0
} else {
0.0
}
}
Column::I64(v, _) => v[i] as f64,
Column::I32(v, _) => v[i] as f64,
Column::Str(_, _) => f64::NAN,
Column::Datetime(v) => v[i] as f64,
}
}
fn as_bool_vec(&self) -> Result<Vec<bool>> {
match self {
Column::Bool(v, _) => Ok(v.to_vec()),
other => Err(VolasError::DType(format!(
"expected a bool column, got {}",
other.dtype()
))),
}
}
fn as_i64_vec(&self) -> Result<Vec<i64>> {
match self {
Column::I64(v, _) => Ok(v.to_vec()),
_ => self
.to_f64_vec()
.iter()
.enumerate()
.map(|(i, &x)| {
if self.is_valid(i) {
i64::try_from_f64(x).ok_or_else(|| {
VolasError::DType(format!("value {x} does not fit int64"))
})
} else {
Ok(0) }
})
.collect(),
}
}
fn as_str_vec(&self) -> Result<Vec<String>> {
match self {
Column::Str(v, _) => Ok(v.to_vec()),
other => Err(VolasError::DType(format!(
"cannot select a {} column as str",
other.dtype()
))),
}
}
fn as_datetime_vec(&self) -> Result<Vec<i64>> {
match self {
Column::Datetime(v) => Ok(v.to_vec()),
other => Err(VolasError::DType(format!(
"cannot select a {} column as datetime",
other.dtype()
))),
}
}
fn as_i32_vec(&self) -> Result<Vec<i32>> {
match self {
Column::I32(v, _) => Ok(v.to_vec()),
Column::Bool(v, _) => Ok(v.iter().map(|&b| b as i32).collect()),
_ => self
.to_f64_vec()
.iter()
.enumerate()
.map(|(i, &x)| {
if self.is_valid(i) {
i32::try_from_f64(x).ok_or_else(|| {
VolasError::DType(format!("value {x} does not fit int32"))
})
} else {
Ok(0) }
})
.collect(),
}
}
pub fn to_f32_vec(&self) -> Vec<f32> {
match self {
Column::F32(v) => v.to_vec(),
_ => self.to_f64_vec().iter().map(|&x| x as f32).collect(),
}
}
fn to_string_vec(&self) -> Vec<String> {
match self {
Column::Str(v, _) => v.to_vec(),
Column::F64(v) => v.iter().map(|x| x.to_string()).collect(),
Column::F32(v) => v.iter().map(|x| x.to_string()).collect(),
Column::I64(v, _) => v.iter().map(|x| x.to_string()).collect(),
Column::I32(v, _) => v.iter().map(|x| x.to_string()).collect(),
Column::Bool(v, _) => v
.iter()
.map(|&b| if b { "True" } else { "False" }.to_string())
.collect(),
Column::Datetime(v) => v.iter().map(|&ns| datetime::format_ns(ns)).collect(),
}
}
}
fn mask_f64(vals: impl Iterator<Item = f64>, validity: &Validity) -> Vec<f64> {
if validity.has_nulls() {
vals.enumerate()
.map(|(i, x)| if validity.is_valid(i) { x } else { f64::NAN })
.collect()
} else {
vals.collect()
}
}
fn widen_i64<T: Copy + Into<i64>>(v: &[T]) -> Vec<i64> {
v.iter().map(|&x| x.into()).collect()
}
fn from_option_bools(n: usize, it: impl Iterator<Item = Option<bool>>) -> Column {
let (mut values, mut valid) = (Vec::with_capacity(n), Vec::with_capacity(n));
for o in it {
values.push(o.unwrap_or(false));
valid.push(o.is_some());
}
Column::bool_with(values, Validity::from_valid_iter(n, valid))
}
fn shift_fill<T: Copy>(v: &[T], n: isize, fill: T) -> Vec<T> {
let len = v.len();
let mut out = vec![fill; len];
if n >= 0 {
let n = (n as usize).min(len);
out[n..].copy_from_slice(&v[..len - n]);
} else {
let n = ((-n) as usize).min(len);
out[..len - n].copy_from_slice(&v[n..]);
}
out
}
fn diff_kernel<T: Copy + std::ops::Sub<Output = T>>(v: &[T], n: isize, missing: T) -> Vec<T> {
let len = v.len();
let mut out = vec![missing; len];
if n >= 0 {
let k = n as usize;
for i in k..len {
out[i] = v[i] - v[i - k];
}
} else {
let k = (-n) as usize;
for i in 0..len.saturating_sub(k) {
out[i] = v[i] - v[i + k];
}
}
out
}
fn append_validity(av: &mut Validity, alen: usize, bv: &Validity, blen: usize) {
if !av.has_nulls() && !bv.has_nulls() {
return;
}
let flags: Vec<bool> = (0..alen)
.map(|i| av.is_valid(i))
.chain((0..blen).map(|i| bv.is_valid(i)))
.collect();
*av = Validity::from_valid_iter(alen + blen, flags);
}
fn sum_valid<T: Numeric>(v: &[T], val: &Validity) -> T {
if !val.has_nulls() {
return stats::sum(v);
}
v.iter()
.enumerate()
.filter(|(i, _)| val.is_valid(*i))
.fold(T::ZERO, |a, (_, &x)| a.wrapping_add(x))
}
fn prod_valid<T: Numeric>(v: &[T], val: &Validity) -> T {
if !val.has_nulls() {
return stats::prod(v);
}
v.iter()
.enumerate()
.filter(|(i, _)| val.is_valid(*i))
.fold(T::ONE, |a, (_, &x)| a.wrapping_mul(x))
}
fn extreme_valid<T: Numeric>(v: &[T], val: &Validity, want_max: bool) -> Option<T> {
if !val.has_nulls() {
return stats::extreme(v, want_max);
}
let mut it = v
.iter()
.enumerate()
.filter(|(i, _)| val.is_valid(*i))
.map(|(_, &x)| x);
let first = it.next()?;
Some(if want_max {
it.fold(first, |a, x| if x > a { x } else { a })
} else {
it.fold(first, |a, x| if x < a { x } else { a })
})
}
fn cum_valid<T: Copy>(v: &[T], val: &Validity, placeholder: T, op: impl Fn(T, T) -> T) -> Vec<T> {
let mut acc: Option<T> = None;
v.iter()
.enumerate()
.map(|(i, &x)| {
if val.is_valid(i) {
let next = acc.map_or(x, |a| op(a, x));
acc = Some(next);
next
} else {
placeholder
}
})
.collect()
}
fn cum<T: Numeric>(
v: &[T],
val: &Validity,
dense: fn(&[T]) -> Vec<T>,
op: fn(T, T) -> T,
) -> Column {
let out = if val.has_nulls() {
cum_valid(v, val, T::ZERO, op)
} else {
dense(v)
};
T::into_column(out).with_validity(val.clone())
}
fn round_f64(x: f64, decimals: i32) -> f64 {
if x.is_nan() {
return x;
}
let f = 10f64.powi(decimals);
(x * f).round_ties_even() / f
}
fn round_i64(x: i64, decimals: i32) -> i64 {
if decimals >= 0 {
return x;
}
let factor = match 10i64.checked_pow(decimals.unsigned_abs()) {
Some(f) => f,
None => return 0, };
let q = x.div_euclid(factor);
let r = x.rem_euclid(factor); let half = factor / 2; let up = r > half || (r == half && q.rem_euclid(2) != 0); if up { q + 1 } else { q }.wrapping_mul(factor)
}
fn clip_vec<T: Numeric>(v: &[T], lo: Option<f64>, hi: Option<f64>) -> Vec<T> {
let lo = lo.and_then(T::try_from_f64);
let hi = hi.and_then(T::try_from_f64);
v.iter()
.map(|&x| {
if x.is_missing() {
return x;
}
let mut y = x;
if let Some(l) = lo {
if y < l {
y = l;
}
}
if let Some(h) = hi {
if y > h {
y = h;
}
}
y
})
.collect()
}
fn cmp_typed(n: usize, op: CmpOp, key: impl Fn(usize) -> Option<Ordering>) -> Vec<bool> {
(0..n)
.map(|i| match key(i) {
Some(ord) => op.matches(ord),
None => op == CmpOp::Ne,
})
.collect()
}
fn ifloordiv(a: i64, b: i64) -> i64 {
let q = a.wrapping_div(b);
let r = a.wrapping_rem(b);
if r != 0 && (r < 0) != (b < 0) {
q - 1
} else {
q
}
}
fn float_key(x: f64) -> Option<u64> {
if x.is_nan() {
None
} else if x == 0.0 {
Some(0)
} else {
Some(x.to_bits())
}
}
fn group_by<K: Eq + std::hash::Hash>(
len: usize,
key: impl Fn(usize) -> Option<K>,
) -> Vec<(usize, usize, bool)> {
let mut order: Vec<(usize, usize, bool)> = Vec::new();
let mut seen: HashMap<K, usize> = HashMap::new();
let mut na: Option<usize> = None;
for i in 0..len {
match key(i) {
None => match na {
Some(g) => order[g].1 += 1,
None => {
na = Some(order.len());
order.push((i, 1, true));
}
},
Some(k) => match seen.get(&k) {
Some(&g) => order[g].1 += 1,
None => {
seen.insert(k, order.len());
order.push((i, 1, false));
}
},
}
}
order
}
fn bool_running(v: &[bool], or: bool) -> Vec<bool> {
let mut acc = !or; v.iter()
.map(|&b| {
acc = if or { acc || b } else { acc && b };
acc
})
.collect()
}
fn clip_bool(v: &[bool], lo: Option<f64>, hi: Option<f64>) -> Vec<bool> {
let force_true = lo.is_some_and(|x| x != 0.0);
let force_false = hi == Some(0.0);
v.iter()
.map(|&b| {
if force_false {
false
} else if force_true {
true
} else {
b
}
})
.collect()
}
fn binary_kernel<T: Numeric>(a: &[T], b: &[T], op: BinOp) -> Vec<T> {
a.iter()
.zip(b)
.map(|(&x, &y)| match op {
BinOp::Add => x.wrapping_add(y),
BinOp::Sub => x.wrapping_sub(y),
BinOp::Mul => x.wrapping_mul(y),
})
.collect()
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_na;