use polars::prelude::*;
macro_rules! kinds {
($($(#[$doc:meta])* $kind:ident($value:ty) => $dtype:expr, $build:expr;)*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
$($(#[$doc])* $kind,)*
}
impl Kind {
pub fn dtype(self) -> DataType {
match self {
$(Kind::$kind => $dtype,)*
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Cell {
$($kind(Option<$value>),)*
}
#[derive(Debug)]
enum Values {
$($kind(Vec<Option<$value>>),)*
}
impl Values {
fn new(kind: Kind) -> Self {
match kind {
$(Kind::$kind => Values::$kind(Vec::new()),)*
}
}
fn push(&mut self, cell: Cell) {
match (self, cell) {
$((Values::$kind(v), Cell::$kind(x)) => v.push(x),)*
(values, _) => {
debug_assert!(false, "a cell of the wrong kind for {values:?}");
values.push_null();
}
}
}
fn push_null(&mut self) {
match self {
$(Values::$kind(v) => v.push(None),)*
}
}
fn take(&mut self, name: PlSmallStr) -> PolarsResult<Series> {
match self {
$(Values::$kind(v) => {
let build: fn(PlSmallStr, Vec<Option<$value>>) -> PolarsResult<Series> =
$build;
build(name, std::mem::take(v))
})*
}
}
}
};
}
kinds! {
Time(i64) => DataType::Datetime(TimeUnit::Milliseconds, Some(TimeZone::UTC)), |name, v| {
Ok(Int64Chunked::from_iter_options(name, v.into_iter())
.into_datetime(TimeUnit::Milliseconds, Some(TimeZone::UTC))
.into_series())
};
DatetimeUs(i64) => DataType::Datetime(TimeUnit::Microseconds, None), |name, v| {
Ok(Int64Chunked::from_iter_options(name, v.into_iter())
.into_datetime(TimeUnit::Microseconds, None)
.into_series())
};
DurationUs(i64) => DataType::Duration(TimeUnit::Microseconds), |name, v| {
Ok(Int64Chunked::from_iter_options(name, v.into_iter())
.into_duration(TimeUnit::Microseconds)
.into_series())
};
F64(f64) => DataType::Float64, |name, v| Ok(Series::new(name, v));
U8(u8) => DataType::UInt8, |name, v| Ok(Series::new(name, v));
U16(u16) => DataType::UInt16, |name, v| Ok(Series::new(name, v));
U32(u32) => DataType::UInt32, |name, v| Ok(Series::new(name, v));
U64(u64) => DataType::UInt64, |name, v| Ok(Series::new(name, v));
I32(i32) => DataType::Int32, |name, v| Ok(Series::new(name, v));
Str(String) => DataType::String, |name, v| Ok(Series::new(name, v));
Label(&'static str) => DataType::String, |name, v| Ok(Series::new(name, v));
Shared(std::sync::Arc<str>) => DataType::String, |name, v| {
Ok(StringChunked::from_iter_options(name, v.iter().map(|s| s.as_deref())).into_series())
};
Bool(bool) => DataType::Boolean, |name, v| Ok(Series::new(name, v));
Binary(Vec<u8>) => DataType::Binary, |name, v| {
Ok(BinaryChunked::from_iter_options(name, v.into_iter()).into_series())
};
ListU32(Vec<u32>) => DataType::List(Box::new(DataType::UInt32)), |name, v| {
let list: ListChunked = v
.into_iter()
.map(|items| items.map(|items| Series::new(PlSmallStr::EMPTY, items)))
.collect();
list.with_name(name)
.into_series()
.cast(&DataType::List(Box::new(DataType::UInt32)))
};
}
pub fn series(
name: &str,
kind: Kind,
cells: impl IntoIterator<Item = Cell>,
) -> PolarsResult<Series> {
let mut values = Values::new(kind);
for cell in cells {
values.push(cell);
}
values.take(name.into())
}
#[derive(Debug)]
pub struct Builder {
names: Vec<String>,
values: Vec<Values>,
len: usize,
}
impl Builder {
pub fn new(columns: &[(&str, Kind)]) -> Self {
let mut builder = Self {
names: Vec::new(),
values: Vec::new(),
len: 0,
};
for (name, kind) in columns {
builder.add_column(name, *kind);
}
builder
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn add_column(&mut self, name: &str, kind: Kind) -> usize {
let mut values = Values::new(kind);
for _ in 0..self.len {
values.push_null();
}
self.names.push(name.to_string());
self.values.push(values);
self.values.len() - 1
}
pub fn push(&mut self, row: impl IntoIterator<Item = Cell>) {
let mut row = row.into_iter();
for values in &mut self.values {
match row.next() {
Some(cell) => values.push(cell),
None => values.push_null(),
}
}
self.len += 1;
}
pub fn push_sparse(&mut self, cells: impl IntoIterator<Item = (usize, Cell)>) {
let mut row: Vec<Option<Cell>> = vec![None; self.values.len()];
for (at, cell) in cells {
if let Some(slot) = row.get_mut(at) {
*slot = Some(cell);
}
}
for (values, cell) in self.values.iter_mut().zip(row) {
match cell {
Some(cell) => values.push(cell),
None => values.push_null(),
}
}
self.len += 1;
}
pub fn set_time(&mut self, column: usize, row: usize, ms: i64) {
if let Some(Values::Time(v)) = self.values.get_mut(column)
&& let Some(slot) = v.get_mut(row)
{
*slot = Some(ms);
}
}
pub fn take(&mut self) -> PolarsResult<DataFrame> {
let height = self.len;
let columns = self
.values
.iter_mut()
.zip(&self.names)
.map(|(values, name)| Ok(values.take(name.as_str().into())?.into_column()))
.collect::<PolarsResult<Vec<_>>>()?;
self.len = 0;
DataFrame::new(height, columns)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rows_become_typed_columns() {
let mut b = Builder::new(&[("t", Kind::Time), ("x", Kind::F64), ("s", Kind::ListU32)]);
b.push([
Cell::Time(Some(1_000)),
Cell::F64(Some(1.5)),
Cell::ListU32(Some(vec![3, 7])),
]);
b.push([Cell::Time(None)]);
let late = b.add_column("late", Kind::Str);
b.push_sparse([(late, Cell::Str(Some("y".into())))]);
b.set_time(0, 1, 2_000);
let df = b.take().unwrap();
assert_eq!(df.height(), 3);
assert_eq!(df.column("t").unwrap().dtype(), &Kind::Time.dtype());
assert_eq!(df.column("s").unwrap().dtype(), &Kind::ListU32.dtype());
assert_eq!(df.column("late").unwrap().null_count(), 2);
assert_eq!(df.column("t").unwrap().null_count(), 1);
assert!(b.is_empty());
b.push([]);
let df = b.take().unwrap();
assert_eq!(df.column("s").unwrap().dtype(), &Kind::ListU32.dtype());
}
}