From cd07ef38d7527c947aef3f6e80d4ef0d7e4da27a Mon Sep 17 00:00:00 2001 From: Peter Bower <37089506+pbower@users.noreply.github.com> Date: Sat, 15 Aug 2026 18:16:10 +0100 Subject: [PATCH] Extend Display trait implementation to all constituent entities --- Cargo.toml | 10 +- examples/print.rs | 341 ++++++++++++++++++ examples/print/print_arrays.rs | 234 ------------ examples/print/print_table.rs | 118 ------ src/enums/value/impls.rs | 74 ++++ src/structs/chunked/super_ndarray.rs | 26 ++ src/structs/cube.rs | 26 ++ src/structs/matrix.rs | 20 + src/structs/ndarray.rs | 19 + src/structs/views/chunked/super_array_view.rs | 19 + .../views/chunked/super_ndarray_view.rs | 26 ++ src/structs/views/chunked/super_table_view.rs | 23 ++ src/structs/views/ndarray_view.rs | 23 ++ src/structs/xarray.rs | 28 ++ src/traits/print.rs | 105 ++++++ 15 files changed, 733 insertions(+), 359 deletions(-) create mode 100644 examples/print.rs delete mode 100644 examples/print/print_arrays.rs delete mode 100644 examples/print/print_table.rs diff --git a/Cargo.toml b/Cargo.toml index 22c4de4..c9166f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -412,14 +412,10 @@ path = "examples/ffi/apache_arrow_ffi.rs" name = "polars_ffi" path = "examples/ffi/polars_ffi.rs" -# Print examples +# Print example [[example]] -name = "print_arrays" -path = "examples/print/print_arrays.rs" - -[[example]] -name = "print_table" -path = "examples/print/print_table.rs" +name = "print" +path = "examples/print.rs" # Selection example [[example]] diff --git a/examples/print.rs b/examples/print.rs new file mode 100644 index 0000000..7efda50 --- /dev/null +++ b/examples/print.rs @@ -0,0 +1,341 @@ +// Copyright 2025 Peter Garfield Bower +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! --------------------------------------------------------- +//! Display output for every printable Minarrow type. +//! +//! Run with: +//! cargo run --example print \ +//! --features value_type,views,chunked,scalar_type,matrix,ndarray,xarray,cube,select +//! --------------------------------------------------------- + +fn main() { + arrays_section(); + table_section(); + + #[cfg(feature = "matrix")] + matrix_section(); + #[cfg(feature = "ndarray")] + ndarray_section(); + #[cfg(all(feature = "ndarray", feature = "views", feature = "select"))] + ndarray_view_section(); + #[cfg(all(feature = "ndarray", feature = "chunked"))] + super_ndarray_section(); + #[cfg(feature = "chunked")] + super_array_section(); + #[cfg(feature = "chunked")] + super_table_section(); + #[cfg(feature = "xarray")] + xarray_section(); + #[cfg(feature = "cube")] + cube_section(); + #[cfg(feature = "value_type")] + value_section(); +} + +fn arrays_section() { + use std::sync::Arc; + + use minarrow::aliases::{BoolArr, FltArr, IntArr, StrArr}; + use minarrow::enums::array::Array; + use minarrow::{Bitmask, MaskedArray, NumericArray, Print, TextArray}; + + let col_i32 = IntArr::::from_slice(&[1, 2, 3, 4, 5]); + let col_u32 = IntArr::::from_slice(&[100, 200, 300, 400, 500]); + let col_f32 = FltArr::::from_slice(&[1.1, 2.2, 3.3, 4.4, 5.5]); + + // Boolean with nulls + let mut col_bool = BoolArr::from_slice(&[true, false, true, false, true]); + col_bool.set_null_mask(Some(Bitmask::from_bools(&[true, true, true, false, true]))); + + let col_str32 = StrArr::from_slice(&["red", "blue", "green", "yellow", "purple"]); + + println!("===== Enums: NumericArray, TextArray =====\n"); + NumericArray::Int32(Arc::new(col_i32.clone())).print(); + println!(); + NumericArray::UInt32(Arc::new(col_u32.clone())).print(); + println!(); + NumericArray::Float32(Arc::new(col_f32.clone())).print(); + println!(); + TextArray::String32(Arc::new(col_str32.clone())).print(); + + println!("\n===== Array (top-level) =====\n"); + Array::from_int32(col_i32.clone()).print(); + println!(); + Array::from_uint32(col_u32.clone()).print(); + println!(); + Array::from_float32(col_f32.clone()).print(); + println!(); + Array::from_string32(col_str32.clone()).print(); + + #[cfg(feature = "views")] + { + use minarrow::{ArrayV, BitmaskV, NumericArrayV, TextArrayV}; + + println!("\n===== Array views =====\n"); + ArrayV::new(Array::from_int32(col_i32.clone()), 1, 3).print(); + + let num_arr = NumericArray::Int32(Arc::new(col_i32.clone())); + NumericArrayV::new(num_arr, 1, 3).print(); + + let txt_arr = TextArray::String32(Arc::new(col_str32.clone())); + TextArrayV::new(txt_arr, 1, 3).print(); + + println!("\n===== Bitmask & BitmaskV =====\n"); + let bm = Bitmask::from_bools(&[true, false, true, true, false]); + bm.print(); + BitmaskV::new(&bm, 1, 3).print(); + } + + // Datetime - various time units + #[cfg(feature = "datetime")] + { + use minarrow::DatetimeArray; + use minarrow::enums::time_units::TimeUnit; + + println!("\n===== Datetime arrays (various time units) =====\n"); + + // Seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + let dt_seconds = DatetimeArray::::from_slice( + &[1_700_000_000, 1_700_086_400, 1_700_172_800], + Some(TimeUnit::Seconds), + ); + println!("Seconds:"); + dt_seconds.print(); + println!(); + + let dt_millis = DatetimeArray::::from_slice( + &[1_700_000_000_000, 1_700_086_400_000, 1_700_172_800_000], + Some(TimeUnit::Milliseconds), + ); + println!("Milliseconds:"); + dt_millis.print(); + println!(); + + let dt_days = + DatetimeArray::::from_slice(&[19_670, 19_671, 19_672], Some(TimeUnit::Days)); + println!("Days:"); + dt_days.print(); + + // Timezone conversions (requires the datetime_ops feature) + #[cfg(feature = "datetime_ops")] + { + println!("\n===== Datetime with timezone conversions =====\n"); + let utc_dt = DatetimeArray::::from_slice(&[1_700_000_000], Some(TimeUnit::Seconds)); + println!("America/New_York:"); + utc_dt.tz("America/New_York").print(); + println!("\nAustralia/Sydney:"); + utc_dt.tz("Australia/Sydney").print(); + println!("\n+05:30 (India):"); + utc_dt.tz("+05:30").print(); + } + } +} + +fn table_section() { + use minarrow::aliases::{BoolArr, FltArr, IntArr, StrArr}; + use minarrow::{Bitmask, FieldArray, MaskedArray, Print, Table}; + + println!("\n===== Table =====\n"); + + let col_i32 = IntArr::::from_slice(&[1, 2, 3, 4, 5]); + let col_u64 = IntArr::::from_slice(&[101, 201, 301, 401, 501]); + let col_f64 = FltArr::::from_slice(&[2.2, 3.3, 4.4, 5.5, 6.6]); + + let mut col_bool = BoolArr::from_slice(&[true, false, true, false, true]); + col_bool.set_null_mask(Some(Bitmask::from_bools(&[true, true, true, false, true]))); + + let col_str32 = StrArr::::from_slice(&["red", "blue", "green", "yellow", "purple"]); + + let mut table = Table::new("MyTable".to_string(), None); + table.add_col(FieldArray::from_arr("int32_col", col_i32)); + table.add_col(FieldArray::from_arr("uint64_col", col_u64)); + table.add_col(FieldArray::from_arr("float64_col", col_f64)); + table.add_col(FieldArray::from_arr("bool_col", col_bool)); + table.add_col(FieldArray::from_arr("utf8_col", col_str32)); + + #[cfg(any(not(feature = "default_categorical_8"), feature = "extended_categorical"))] + { + use minarrow::aliases::CatArr; + let col_cat32 = CatArr::::from_values( + ["apple", "banana", "cherry", "banana", "apple"].iter().copied(), + ); + table.add_col(FieldArray::from_arr("dict32_col", col_cat32)); + } + + #[cfg(feature = "datetime")] + { + use minarrow::DatetimeArray; + use minarrow::enums::time_units::TimeUnit; + let col_dt64 = DatetimeArray::::from_slice( + &[1_000_000_000, 2_000_000_000, 3_000_000_000, 4_000_000_000, 5_000_000_000], + Some(TimeUnit::Nanoseconds), + ); + table.add_col(FieldArray::from_arr("datetime64_col", col_dt64)); + } + + table.print(); +} + +#[cfg(feature = "matrix")] +fn matrix_section() { + use minarrow::{Matrix, Print, mat}; + + println!("\n===== Matrix =====\n"); + mat![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]].print(); + + // A matrix taller than the preview shows its first and last ten rows. + println!("\n----- Matrix (preview elision) -----\n"); + let tall: Vec = (0..120).map(|x| x as f64).collect(); + Matrix::from_f64_unaligned(&tall, 60, 2, Some("tall".to_string())).print(); +} + +#[cfg(feature = "ndarray")] +fn ndarray_section() { + use minarrow::{NdArray, Print}; + + println!("\n===== NdArray =====\n"); + println!("----- rank 1 -----\n"); + NdArray::from_slice(&[10.0, 20.0, 30.0, 40.0], &[4]).print(); + + println!("\n----- rank 2 -----\n"); + NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]).print(); + + println!("\n----- rank 3 (trailing axes flatten into columns) -----\n"); + NdArray::from_slice(&(0..24).map(|x| x as f64).collect::>(), &[2, 3, 4]).print(); +} + +#[cfg(all(feature = "ndarray", feature = "views", feature = "select"))] +fn ndarray_view_section() { + use minarrow::{NdArray, Print, nd}; + + println!("\n===== NdArrayView =====\n"); + let nd2 = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]); + nd2.slice(nd![1..3, 0..2]).print(); +} + +#[cfg(all(feature = "ndarray", feature = "chunked"))] +fn super_ndarray_section() { + use minarrow::{NdArray, Print, SuperNdArray}; + + println!("\n===== SuperNdArray =====\n"); + let batch_a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]); + let batch_b = NdArray::from_slice(&[5.0, 6.0, 7.0, 8.0, 9.0, 10.0], &[3, 2]); + let super_nd = SuperNdArray::from_batches(vec![batch_a, batch_b], "sensor"); + super_nd.print(); + + #[cfg(feature = "views")] + { + println!("\n===== SuperNdArrayView =====\n"); + super_nd.slice(1, 3).print(); + } +} + +#[cfg(feature = "chunked")] +fn super_array_section() { + use minarrow::{Print, SuperArray, fa_i64}; + + println!("\n===== SuperArray =====\n"); + let super_arr = + SuperArray::from_chunks(vec![fa_i64!("price", 100, 200, 300), fa_i64!("price", 400, 500)]); + super_arr.print(); + + #[cfg(feature = "views")] + { + println!("\n===== SuperArrayView =====\n"); + super_arr.slice(1, 3).print(); + } +} + +#[cfg(feature = "chunked")] +fn super_table_section() { + use std::sync::Arc; + + use minarrow::{Print, SuperTable, fa_f64, fa_i64, tbl}; + + println!("\n===== SuperTable =====\n"); + let batch_1 = tbl!("b1", fa_i64!("qty", 1, 2), fa_f64!("px", 9.5, 10.5)); + let batch_2 = tbl!("b2", fa_i64!("qty", 3, 4, 5), fa_f64!("px", 11.5, 12.5, 13.5)); + let super_tbl = SuperTable::from_batches( + vec![Arc::new(batch_1), Arc::new(batch_2)], + Some("orders".to_string()), + ); + super_tbl.print(); + + #[cfg(feature = "views")] + { + println!("\n===== SuperTableView =====\n"); + super_tbl.view(1, 3).print(); + } +} + +#[cfg(feature = "xarray")] +fn xarray_section() { + use minarrow::{NdArray, Print, XArray, arr_f64}; + + println!("\n===== XArray =====\n"); + let mut xa = XArray::new( + NdArray::from_slice(&[20.1, 20.4, 20.9, 1.01, 1.02, 1.00], &[3, 2]), + &["hour", "measurement"], + ); + xa.assign_coords("hour", arr_f64![0.0, 1.0, 2.0]); + xa.print(); +} + +#[cfg(feature = "cube")] +fn cube_section() { + use minarrow::{Cube, Print, fa_bool, fa_i64, tbl}; + + println!("\n===== Cube =====\n"); + let source = tbl!( + "trades", + fa_i64!("id", 1, 2, 1, 3), + fa_bool!("flag", true, false, true, false) + ); + Cube::from_table(&source, "id", "grouped").unwrap().print(); +} + +#[cfg(feature = "value_type")] +fn value_section() { + use std::sync::Arc; + + use minarrow::{Print, Value, fa_i64, tbl}; + + println!("\n===== Value =====\n"); + let table = tbl!("px", fa_i64!("qty", 1, 2, 3)); + Value::Table(Arc::new(table.clone())).print(); + + println!("\n----- Value::Tuple2 -----\n"); + Value::Tuple2(Arc::new(( + Value::Table(Arc::new(table.clone())), + Value::Table(Arc::new(table.clone())), + ))) + .print(); + + println!("\n----- Value::VecValue -----\n"); + Value::VecValue(Arc::new(vec![ + Value::Table(Arc::new(table.clone())), + Value::Table(Arc::new(table)), + ])) + .print(); + + #[cfg(feature = "matrix")] + { + use minarrow::{Matrix, mat}; + + println!("\n----- Value::Matrix -----\n"); + let matrix: Matrix = mat![[1.0, 2.0], [3.0, 4.0]]; + Value::Matrix(Arc::new(matrix)).print(); + } +} diff --git a/examples/print/print_arrays.rs b/examples/print/print_arrays.rs deleted file mode 100644 index 9888d77..0000000 --- a/examples/print/print_arrays.rs +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2025 Peter Garfield Bower -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! --------------------------------------------------------- -//! Builds a demo table and prints it. -//! -//! Run with: -//! cargo run --example print_arrays -//! --------------------------------------------------------- - -use std::sync::Arc; - -use minarrow::aliases::{BoolArr, FltArr, IntArr, StrArr}; -use minarrow::enums::array::Array; -use minarrow::{Bitmask, MaskedArray, NumericArray, Print, TextArray}; - -#[cfg(feature = "views")] -use minarrow::{ArrayV, BitmaskV, NumericArrayV, TextArrayV}; - -#[cfg(feature = "datetime")] -use minarrow::DatetimeArray; - -fn main() { - // Numeric (Integer, Float, all sizes) - let col_i32 = IntArr::::from_slice(&[1, 2, 3, 4, 5]); - let col_u32 = IntArr::::from_slice(&[100, 200, 300, 400, 500]); - let col_f32 = FltArr::::from_slice(&[1.1, 2.2, 3.3, 4.4, 5.5]); - - // Boolean with nulls - let mut col_bool = BoolArr::from_slice(&[true, false, true, false, true]); - col_bool.set_null_mask(Some(Bitmask::from_bools(&[true, true, true, false, true]))); - - // String and Dictionary/Categorical - let col_str32 = StrArr::from_slice(&["red", "blue", "green", "yellow", "purple"]); - - // --- Print NumericArray, TextArray, TemporalArray enums - println!("\n--- Enums: NumericArray, TextArray, TemporalArray ---"); - NumericArray::Int32(Arc::new(col_i32.clone())).print(); - println!("\n"); - NumericArray::UInt32(Arc::new(col_u32.clone())).print(); - println!("\n"); - NumericArray::Float32(Arc::new(col_f32.clone())).print(); - println!("\n"); - TextArray::String32(Arc::new(col_str32.clone())).print(); - println!("\n"); - - println!("\n--- Array (top-level) ---"); - Array::from_int32(col_i32.clone()).print(); - println!("\n"); - Array::from_uint32(col_u32.clone()).print(); - println!("\n"); - Array::from_float32(col_f32.clone()).print(); - println!("\n"); - Array::from_string32(col_str32.clone()).print(); - println!("\n"); - // --- Print Array Views (ArrayV, NumericArrayV, TextArrayV, TemporalArrayV) - #[cfg(feature = "views")] - println!("\n--- Array Views ---"); - #[cfg(feature = "views")] - ArrayV::new(Array::from_int32(col_i32.clone()), 1, 3).print(); - - let num_arr = NumericArray::Int32(Arc::new(col_i32.clone())); - num_arr.print(); - - #[cfg(feature = "views")] - let num_view = NumericArrayV::new(num_arr, 1, 3); - #[cfg(feature = "views")] - num_view.print(); - - let txt_arr = TextArray::String32(Arc::new(col_str32.clone())); - txt_arr.print(); - - #[cfg(feature = "views")] - let txt_view = TextArrayV::new(txt_arr, 1, 3); - #[cfg(feature = "views")] - txt_view.print(); - - // --- Print Bitmask and BitmaskV - println!("\n--- Bitmask & BitmaskV ---"); - let bm = Bitmask::from_bools(&[true, false, true, true, false]); - bm.print(); - #[cfg(feature = "views")] - BitmaskV::new(&bm, 1, 3).print(); - - // Datetime - various time units - #[cfg(feature = "datetime")] - { - use minarrow::enums::time_units::TimeUnit; - - println!("\n--- Datetime Arrays (various time units) ---"); - - // Seconds since Unix epoch (1970-01-01 00:00:00 UTC) - let dt_seconds = DatetimeArray::::from_slice( - &[ - 1_700_000_000, // 2023-11-14 22:13:20 UTC - 1_700_086_400, // 2023-11-15 22:13:20 UTC - 1_700_172_800, // 2023-11-16 22:13:20 UTC - ], - Some(TimeUnit::Seconds), - ); - println!("Seconds:"); - dt_seconds.print(); - println!(); - - // Milliseconds - let dt_millis = DatetimeArray::::from_slice( - &[ - 1_700_000_000_000, // 2023-11-14 22:13:20.000 UTC - 1_700_086_400_000, // 2023-11-15 22:13:20.000 UTC - 1_700_172_800_000, // 2023-11-16 22:13:20.000 UTC - ], - Some(TimeUnit::Milliseconds), - ); - println!("Milliseconds:"); - dt_millis.print(); - println!(); - - // Microseconds - let dt_micros = DatetimeArray::::from_slice( - &[ - 1_700_000_000_000_000, // 2023-11-14 22:13:20.000000 UTC - 1_700_086_400_000_000, // 2023-11-15 22:13:20.000000 UTC - 1_700_172_800_000_000, // 2023-11-16 22:13:20.000000 UTC - ], - Some(TimeUnit::Microseconds), - ); - println!("Microseconds:"); - dt_micros.print(); - println!(); - - // Nanoseconds - let dt_nanos = DatetimeArray::::from_slice( - &[ - 1_700_000_000_000_000_000, // 2023-11-14 22:13:20.000000000 UTC - 1_700_086_400_000_000_000, // 2023-11-15 22:13:20.000000000 UTC - 1_700_172_800_000_000_000, // 2023-11-16 22:13:20.000000000 UTC - ], - Some(TimeUnit::Nanoseconds), - ); - println!("Nanoseconds:"); - dt_nanos.print(); - println!(); - - // Days since Unix epoch - let dt_days = DatetimeArray::::from_slice( - &[ - 19_670, // 2023-11-14 - 19_671, // 2023-11-15 - 19_672, // 2023-11-16 - ], - Some(TimeUnit::Days), - ); - println!("Days:"); - dt_days.print(); - println!(); - - // With timezone operations (requires datetime_ops feature) - #[cfg(feature = "datetime_ops")] - { - println!( - "--- Datetime with Timezone Conversions (requires 'datetime_ops' feature) ---" - ); - - // UTC datetime - let utc_dt = - DatetimeArray::::from_slice(&[1_700_000_000], Some(TimeUnit::Seconds)); - - // Test IANA timezone identifiers - println!("IANA Timezone Identifiers:"); - println!("America/New_York:"); - utc_dt.tz("America/New_York").print(); - println!(); - - println!("Australia/Sydney:"); - utc_dt.tz("Australia/Sydney").print(); - println!(); - - println!("Europe/London:"); - utc_dt.tz("Europe/London").print(); - println!(); - - println!("Asia/Tokyo:"); - utc_dt.tz("Asia/Tokyo").print(); - println!(); - - // Test timezone abbreviations - println!("\nTimezone Abbreviations:"); - println!("EST:"); - utc_dt.tz("EST").print(); - println!(); - - println!("AEST:"); - utc_dt.tz("AEST").print(); - println!(); - - println!("JST:"); - utc_dt.tz("JST").print(); - println!(); - - // Test direct offset strings - println!("\nDirect Offset Strings:"); - println!("UTC:"); - utc_dt.tz("UTC").print(); - println!(); - - println!("+05:30 (India):"); - utc_dt.tz("+05:30").print(); - println!(); - - println!("-03:30 (Newfoundland):"); - utc_dt.tz("-03:30").print(); - println!(); - } - - #[cfg(not(feature = "datetime_ops"))] - { - println!( - "Note: Enable 'datetime_ops' feature for timezone conversions and datetime operations." - ); - println!(); - } - } -} diff --git a/examples/print/print_table.rs b/examples/print/print_table.rs deleted file mode 100644 index a0823b0..0000000 --- a/examples/print/print_table.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2025 Peter Garfield Bower -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! --------------------------------------------------------- -//! Builds a demo table and prints it. -//! -//! Run with: -//! cargo run --example print_table -//! --------------------------------------------------------- - -#[cfg(any( - not(feature = "default_categorical_8"), - feature = "extended_categorical" -))] -use minarrow::aliases::CatArr; -use minarrow::aliases::{BoolArr, FltArr, IntArr, StrArr}; -use minarrow::{Bitmask, FieldArray, MaskedArray, Print, Table}; -#[cfg(feature = "datetime")] -use minarrow::{DatetimeArray, enums::time_units::TimeUnit}; - -fn main() { - // Inner arrays - - // Numeric - let col_i32 = IntArr::::from_slice(&[1, 2, 3, 4, 5]); - let col_u32 = IntArr::::from_slice(&[100, 200, 300, 400, 500]); - let col_i64 = IntArr::::from_slice(&[10, 20, 30, 40, 50]); - let col_u64 = IntArr::::from_slice(&[101, 201, 301, 401, 501]); - let col_f32 = FltArr::::from_slice(&[1.1, 2.2, 3.3, 4.4, 5.5]); - let col_f64 = FltArr::::from_slice(&[2.2, 3.3, 4.4, 5.5, 6.6]); - - // Boolean with nulls - let mut col_bool = BoolArr::from_slice(&[true, false, true, false, true]); - col_bool.set_null_mask(Some(Bitmask::from_bools(&[true, true, true, false, true]))); - - // String and Dictionary/Categorical - let col_str32 = StrArr::::from_slice(&["red", "blue", "green", "yellow", "purple"]); - #[cfg(any( - not(feature = "default_categorical_8"), - feature = "extended_categorical" - ))] - let col_cat32 = CatArr::::from_values( - ["apple", "banana", "cherry", "banana", "apple"] - .iter() - .copied(), - ); - - // Datetime - #[cfg(feature = "datetime")] - let col_dt32 = DatetimeArray::::from_slice( - &[1000, 2000, 3000, 4000, 5000], - Some(TimeUnit::Milliseconds), - ); - #[cfg(feature = "datetime")] - let col_dt64 = DatetimeArray::::from_slice( - &[ - 1_000_000_000, - 2_000_000_000, - 3_000_000_000, - 4_000_000_000, - 5_000_000_000, - ], - Some(TimeUnit::Nanoseconds), - ); - - // FieldArray (column) construction - let fa_i32 = FieldArray::from_arr("int32_col", col_i32); - let fa_u32 = FieldArray::from_arr("uint32_col", col_u32); - let fa_i64 = FieldArray::from_arr("int64_col", col_i64); - let fa_u64 = FieldArray::from_arr("uint64_col", col_u64); - let fa_f32 = FieldArray::from_arr("float32_col", col_f32); - let fa_f64 = FieldArray::from_arr("float64_col", col_f64); - let fa_bool = FieldArray::from_arr("bool_col", col_bool); - let fa_str32 = FieldArray::from_arr("utf8_col", col_str32); - #[cfg(any( - not(feature = "default_categorical_8"), - feature = "extended_categorical" - ))] - let fa_cat32 = FieldArray::from_arr("dict32_col", col_cat32); - #[cfg(feature = "datetime")] - let fa_dt32 = FieldArray::from_arr("datetime32_col", col_dt32); - #[cfg(feature = "datetime")] - let fa_dt64 = FieldArray::from_arr("datetime64_col", col_dt64); - - // Build Table - let mut tbl = Table::new("MyTable".to_string(), None); - tbl.add_col(fa_i32); - tbl.add_col(fa_u32); - tbl.add_col(fa_i64); - tbl.add_col(fa_u64); - tbl.add_col(fa_f32); - tbl.add_col(fa_f64); - tbl.add_col(fa_bool); - tbl.add_col(fa_str32); - #[cfg(any( - not(feature = "default_categorical_8"), - feature = "extended_categorical" - ))] - tbl.add_col(fa_cat32); - #[cfg(feature = "datetime")] - tbl.add_col(fa_dt32); - #[cfg(feature = "datetime")] - tbl.add_col(fa_dt64); - - // Print the table - tbl.print(); -} diff --git a/src/enums/value/impls.rs b/src/enums/value/impls.rs index 68b9a62..f4ef06e 100644 --- a/src/enums/value/impls.rs +++ b/src/enums/value/impls.rs @@ -18,6 +18,7 @@ use crate::enums::shape_dim::ShapeDim; use crate::traits::concatenate::Concatenate; use crate::traits::shape::Shape; use crate::{BooleanArray, FloatArray, IntegerArray, StringArray}; +use std::fmt::{self, Display, Formatter}; use std::sync::Arc; #[cfg(feature = "datetime")] @@ -79,6 +80,79 @@ impl PartialEq for Value { /// we can safely implement Eq. impl Eq for Value {} +impl Display for Value { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use super::Value::*; + + let render_seq = |f: &mut Formatter<'_>, header: &str, elems: &[&Value]| -> fmt::Result { + writeln!(f, "{header}")?; + for (i, value) in elems.iter().enumerate() { + writeln!(f, " ├─ .{i}")?; + for line in format!("{value}").lines() { + writeln!(f, " │ {line}")?; + } + } + Ok(()) + }; + + match self { + #[cfg(feature = "scalar_type")] + Scalar(s) => write!(f, "{s}"), + Array(a) => write!(f, "{a}"), + #[cfg(feature = "views")] + ArrayView(v) => write!(f, "{v}"), + FieldArray(fa) => write!(f, "{fa}"), + Table(t) => write!(f, "{t}"), + #[cfg(feature = "views")] + TableView(tv) => write!(f, "{tv}"), + #[cfg(feature = "chunked")] + SuperArray(sa) => write!(f, "{sa}"), + #[cfg(all(feature = "chunked", feature = "views"))] + SuperArrayView(sav) => write!(f, "{sav}"), + #[cfg(feature = "chunked")] + SuperTable(st) => write!(f, "{st}"), + #[cfg(all(feature = "chunked", feature = "views"))] + SuperTableView(stv) => write!(f, "{stv}"), + #[cfg(feature = "matrix")] + Matrix(m) => write!(f, "{m}"), + #[cfg(feature = "ndarray")] + NdArray(nd) => write!(f, "{nd}"), + #[cfg(all(feature = "ndarray", feature = "views"))] + NdArrayView(v) => write!(f, "{v}"), + #[cfg(all(feature = "ndarray", feature = "chunked"))] + SuperNdArray(snd) => write!(f, "{snd}"), + #[cfg(all(feature = "ndarray", feature = "chunked", feature = "views"))] + SuperNdArrayView(v) => write!(f, "{v}"), + #[cfg(feature = "xarray")] + XArray(xa) => write!(f, "{xa}"), + #[cfg(feature = "cube")] + Cube(c) => write!(f, "{c}"), + + VecValue(values) => { + writeln!(f, "Vec [{} values]", values.len())?; + for (i, value) in values.iter().enumerate() { + writeln!(f, " ├─ [{i}]")?; + for line in format!("{value}").lines() { + writeln!(f, " │ {line}")?; + } + } + Ok(()) + } + + BoxValue(value) => write!(f, "{value}"), + ArcValue(value) => write!(f, "{value}"), + + Tuple2(t) => render_seq(f, "Tuple2", &[&t.0, &t.1]), + Tuple3(t) => render_seq(f, "Tuple3", &[&t.0, &t.1, &t.2]), + Tuple4(t) => render_seq(f, "Tuple4", &[&t.0, &t.1, &t.2, &t.3]), + Tuple5(t) => render_seq(f, "Tuple5", &[&t.0, &t.1, &t.2, &t.3, &t.4]), + Tuple6(t) => render_seq(f, "Tuple6", &[&t.0, &t.1, &t.2, &t.3, &t.4, &t.5]), + + Custom(cv) => write!(f, "{cv:?}"), + } + } +} + // Shape Implementation impl Shape for Value { diff --git a/src/structs/chunked/super_ndarray.rs b/src/structs/chunked/super_ndarray.rs index d5d0caa..22be08b 100644 --- a/src/structs/chunked/super_ndarray.rs +++ b/src/structs/chunked/super_ndarray.rs @@ -30,6 +30,7 @@ //! labels. SuperNdArray remains a separate container. use std::fmt; +use std::fmt::{Display, Formatter}; use crate::enums::error::MinarrowError; use crate::enums::shape_dim::ShapeDim; @@ -894,6 +895,31 @@ impl fmt::Debug for SuperNdArray { } } +impl Display for SuperNdArray { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let shape = self.shape(); + let elem = std::any::type_name::(); + let dims = if shape.is_empty() { + String::from("scalar") + } else { + shape.iter().map(|d| d.to_string()).collect::>().join(" × ") + }; + writeln!( + f, + "SuperNdArray \"{}\" [{}, {} batches, {}]", + self.name, dims, self.n_batches(), elem + )?; + for (i, batch) in self.batches.iter().enumerate() { + writeln!(f, " ├─ Batch {i}: {} elements", batch.len())?; + let indent = " │ "; + for line in format!("{batch}").lines() { + writeln!(f, "{indent}{line}")?; + } + } + Ok(()) + } +} + // **************************************************************** // Tests // **************************************************************** diff --git a/src/structs/cube.rs b/src/structs/cube.rs index 70f70a6..f25fa02 100644 --- a/src/structs/cube.rs +++ b/src/structs/cube.rs @@ -36,6 +36,7 @@ //! Feature-gated and **WIP/unstable**. APIs may evolve. use std::collections::HashMap; +use std::fmt::{self, Display, Formatter}; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -614,6 +615,31 @@ impl Shape for Cube { } } +impl Display for Cube { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if self.tables.is_empty() { + return writeln!(f, "Cube \"{}\" [0 tables]", self.name); + } + let index = match &self.third_dim_index { + Some(cols) => cols.join(", "), + None => String::from("none"), + }; + writeln!( + f, + "Cube \"{}\" [{} tables, {} cols] (index: {})", + self.name, self.n_tables(), self.n_cols(), index + )?; + for (i, table) in self.tables.iter().enumerate() { + writeln!(f, " ├─ Table {i}: \"{}\" [{} rows]", table.name, table.n_rows)?; + let indent = " │ "; + for line in format!("{table}").lines() { + writeln!(f, "{indent}{line}")?; + } + } + Ok(()) + } +} + impl Concatenate for Cube { /// Concatenates two cubes by appending all tables from `other` to `self`. /// diff --git a/src/structs/matrix.rs b/src/structs/matrix.rs index e02a3ec..a01efce 100644 --- a/src/structs/matrix.rs +++ b/src/structs/matrix.rs @@ -18,11 +18,13 @@ //! BLAS/LAPACK compatible with built-inconversions from `Table` data. use std::fmt; +use std::fmt::{Display, Formatter}; use std::sync::Arc; use crate::enums::error::MinarrowError; use crate::enums::shape_dim::ShapeDim; use crate::structs::buffer::Buffer; +use crate::traits::print::print_float_grid; use crate::traits::{concatenate::Concatenate, shape::Shape}; use crate::{Array, Field, FieldArray, FloatArray, NumericArray, Table, Vec64}; #[cfg(feature = "views")] @@ -716,6 +718,24 @@ impl Concatenate for Matrix { } } +impl Display for Matrix { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match &self.name { + Some(name) => writeln!( + f, + "Matrix \"{}\" [{} rows × {} cols, f64]", + name, self.n_rows, self.n_cols + )?, + None => writeln!(f, "Matrix [{} rows × {} cols, f64]", self.n_rows, self.n_cols)?, + } + if self.n_cols == 0 { + return Ok(()); + } + let headers: Vec = (0..self.n_cols).map(|c| format!("col_{c}")).collect(); + print_float_grid(f, &headers, self.n_rows, |row, col| self.get(row, col)) + } +} + // Pretty print impl fmt::Debug for Matrix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/structs/ndarray.rs b/src/structs/ndarray.rs index 89806f9..8e54d9b 100644 --- a/src/structs/ndarray.rs +++ b/src/structs/ndarray.rs @@ -65,12 +65,14 @@ //! layout, and protocol constraints. use std::fmt; +use std::fmt::{Display, Formatter}; use std::ops::{Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo}; use std::sync::Arc; use crate::enums::error::MinarrowError; use crate::enums::shape_dim::ShapeDim; use crate::structs::buffer::Buffer; +use crate::traits::print::print_ndarray_body; #[cfg(all(feature = "views", feature = "select"))] use crate::traits::selection::{AxisSelection, DataSelector, RowSelection}; use crate::traits::type_unions::Float; @@ -1986,6 +1988,23 @@ impl fmt::Debug for NdArray { } } +impl Display for NdArray { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let shape = self.shape(); + let elem = std::any::type_name::(); + let dims = if shape.is_empty() { + String::from("scalar") + } else { + shape.iter().map(|d| d.to_string()).collect::>().join(" × ") + }; + match &self.name { + Some(name) => writeln!(f, "NdArray \"{}\" [{}, {}]", name, dims, elem)?, + None => writeln!(f, "NdArray [{}, {}]", dims, elem)?, + } + print_ndarray_body(f, shape, |index| self.get(index)) + } +} + impl TryFrom> for Table { type Error = MinarrowError; diff --git a/src/structs/views/chunked/super_array_view.rs b/src/structs/views/chunked/super_array_view.rs index 3ff434c..1d41446 100644 --- a/src/structs/views/chunked/super_array_view.rs +++ b/src/structs/views/chunked/super_array_view.rs @@ -45,6 +45,7 @@ //! - `len` is the logical row count of this view. //! - `slices` are ordered, non-overlapping, and cover at most `len` rows. //! - `field` is the schema for the underlying array and is shared by all slices. +use std::fmt::{self, Display, Formatter}; use std::sync::Arc; use crate::{ @@ -302,6 +303,24 @@ impl From for SuperArray { } } +impl Display for SuperArrayV { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!( + f, + "SuperArrayView \"{}\" [{} rows, {} slices] (dtype: {})", + self.field.name, self.len, self.n_slices(), self.field.dtype + )?; + for (i, slice) in self.slices.iter().enumerate() { + writeln!(f, " ├─ Slice {i}: {} rows", slice.len())?; + let indent = " │ "; + for line in format!("{slice}").lines() { + writeln!(f, "{indent}{line}")?; + } + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/structs/views/chunked/super_ndarray_view.rs b/src/structs/views/chunked/super_ndarray_view.rs index fabf4ab..6919868 100644 --- a/src/structs/views/chunked/super_ndarray_view.rs +++ b/src/structs/views/chunked/super_ndarray_view.rs @@ -34,6 +34,7 @@ //! - `n_obs` is the logical axis-0 observation count of this view. use std::fmt; +use std::fmt::{Display, Formatter}; use crate::enums::error::MinarrowError; use crate::enums::shape_dim::ShapeDim; @@ -437,6 +438,31 @@ impl fmt::Debug for SuperNdArrayV { } } +impl Display for SuperNdArrayV { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let shape = self.shape(); + let elem = std::any::type_name::(); + let dims = if shape.is_empty() { + String::from("scalar") + } else { + shape.iter().map(|d| d.to_string()).collect::>().join(" × ") + }; + writeln!( + f, + "SuperNdArrayView \"{}\" [{}, {} slices, {}]", + self.name(), dims, self.n_slices(), elem + )?; + for (i, slice) in self.slices.iter().enumerate() { + writeln!(f, " ├─ Slice {i}: {} elements", slice.len())?; + let indent = " │ "; + for line in format!("{slice}").lines() { + writeln!(f, "{indent}{line}")?; + } + } + Ok(()) + } +} + /// SuperNdArray -> SuperNdArrayV conversion. Each batch becomes a full /// axis-0 slice view, keeping the parent batches alive through each /// batch's shared internal buffer. diff --git a/src/structs/views/chunked/super_table_view.rs b/src/structs/views/chunked/super_table_view.rs index 74a7d46..0ba992c 100644 --- a/src/structs/views/chunked/super_table_view.rs +++ b/src/structs/views/chunked/super_table_view.rs @@ -46,6 +46,7 @@ //! - `slices` are ordered, non-overlapping, and each covers a contiguous region //! within its underlying table batch. +use std::fmt::{self, Display, Formatter}; use std::sync::Arc; use crate::enums::error::MinarrowError; @@ -254,6 +255,28 @@ impl From for SuperTableV { } } +impl Display for SuperTableV { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!( + f, + "SuperTableView [{} rows, {} columns, {} slices]", + self.len, self.n_cols(), self.n_slices() + )?; + for (i, slice) in self.slices.iter().enumerate() { + writeln!( + f, + " ├─ Slice {i}: {} rows, {} columns", + slice.n_rows(), slice.n_cols() + )?; + let indent = " │ "; + for line in format!("{slice}").lines() { + writeln!(f, "{indent}{line}")?; + } + } + Ok(()) + } +} + #[cfg(feature = "views")] #[cfg(test)] mod tests { diff --git a/src/structs/views/ndarray_view.rs b/src/structs/views/ndarray_view.rs index 662c2fe..b800a54 100644 --- a/src/structs/views/ndarray_view.rs +++ b/src/structs/views/ndarray_view.rs @@ -7,11 +7,13 @@ //! copying data. use std::fmt; +use std::fmt::{Display, Formatter}; use std::ops::Index; use crate::enums::error::MinarrowError; use crate::enums::shape_dim::ShapeDim; use crate::structs::ndarray::{NdArray, NdArrayIter, NdDims, offset_of_impl}; +use crate::traits::print::print_ndarray_body; #[cfg(feature = "select")] use crate::structs::ndarray::gather_obs_impl; #[cfg(feature = "select")] @@ -616,6 +618,27 @@ impl fmt::Debug for NdArrayV { } } +impl Display for NdArrayV { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let shape = self.shape(); + let elem = std::any::type_name::(); + let dims = if shape.is_empty() { + String::from("scalar") + } else { + shape.iter().map(|d| d.to_string()).collect::>().join(" × ") + }; + match &self.source.name { + Some(name) => writeln!( + f, + "NdArrayView \"{}\" [{}, {}] (offset: {})", + name, dims, elem, self.offset + )?, + None => writeln!(f, "NdArrayView [{}, {}] (offset: {})", dims, elem, self.offset)?, + } + print_ndarray_body(f, shape, |index| self.get(index)) + } +} + impl From> for NdArray { /// Materialises the viewed window as an owned contiguous array. fn from(value: NdArrayV) -> Self { diff --git a/src/structs/xarray.rs b/src/structs/xarray.rs index b601192..38ae77b 100644 --- a/src/structs/xarray.rs +++ b/src/structs/xarray.rs @@ -35,6 +35,7 @@ //! ` use std::fmt; +use std::fmt::{Display, Formatter}; use crate::enums::error::MinarrowError; use crate::enums::shape_dim::ShapeDim; @@ -55,6 +56,7 @@ use crate::{NumericArray, TextArray}; use crate::TemporalArray; #[cfg(all(feature = "views", feature = "select"))] use std::ops::Range; +use crate::traits::print::print_ndarray_body; use crate::traits::type_unions::Float; use crate::traits::{concatenate::Concatenate, shape::Shape}; use crate::{Array, Field, StringArray, Table}; @@ -1195,6 +1197,32 @@ impl fmt::Debug for XArray { } } +impl Display for XArray { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let shape = self.shape(); + let elem = std::any::type_name::(); + let dims: Vec = self + .axes + .iter() + .zip(shape.iter()) + .map(|(axis, &size)| { + if axis.coords.is_some() { + format!("{}={} (labelled)", axis.name, size) + } else { + format!("{}={}", axis.name, size) + } + }) + .collect(); + let dim_desc = if dims.is_empty() { + String::from("scalar") + } else { + dims.join(", ") + }; + writeln!(f, "XArray [{}, {}]", dim_desc, elem)?; + print_ndarray_body(f, &shape, |index| self.get(index)) + } +} + // **************************************************************** // TryFrom diff --git a/src/traits/print.rs b/src/traits/print.rs index 08336d2..8ccd867 100644 --- a/src/traits/print.rs +++ b/src/traits/print.rs @@ -183,6 +183,111 @@ pub(crate) fn format_float(v: T) -> String { } } +/// Render a dense numeric grid in the bordered table layout, eliding +/// rows beyond [`MAX_PREVIEW`]. +#[cfg(any(feature = "matrix", feature = "ndarray"))] +pub(crate) fn print_float_grid( + f: &mut Formatter<'_>, + headers: &[String], + n_rows: usize, + cell: impl Fn(usize, usize) -> T, +) -> fmt::Result { + let n_cols = headers.len(); + + // Show every row for a short grid, otherwise the first and last ten. + let row_indices: Vec = if n_rows <= MAX_PREVIEW { + (0..n_rows).collect() + } else { + let mut idx = (0..10).collect::>(); + idx.extend((n_rows - 10)..n_rows); + idx + }; + + // Each column widens to fit its header and the values shown beneath it. + let mut widths: Vec = headers.iter().map(|h| h.len()).collect(); + let mut rows: Vec> = Vec::with_capacity(row_indices.len()); + for &r in &row_indices { + let mut row = Vec::with_capacity(n_cols); + for c in 0..n_cols { + let text = format_float(cell(r, c)); + widths[c] = widths[c].max(text.len()); + row.push(text); + } + rows.push(row); + } + + let idx_width = usize::max( + 3, + ((n_rows.saturating_sub(1)) as f64).log10().floor() as usize + 1, + ); + + print_rule(f, idx_width, &widths)?; + print_header_row(f, idx_width, headers, &widths)?; + print_rule(f, idx_width, &widths)?; + + for (logical_row, cells) in rows.iter().enumerate() { + let physical_row = row_indices[logical_row]; + write!(f, "| {idx:^w$} |", idx = physical_row, w = idx_width)?; + for (c, text) in cells.iter().enumerate() { + write!(f, " {val:^w$} |", val = text, w = widths[c])?; + } + writeln!(f)?; + if logical_row == 9 && n_rows > MAX_PREVIEW { + print_ellipsis_row(f, idx_width, &widths)?; + } + } + print_rule(f, idx_width, &widths) +} + +/// Render the body of an N-dimensional float array beneath a caller-written +/// title, with the leading axis as rows and trailing axes flattened into +/// columns. +#[cfg(feature = "ndarray")] +pub(crate) fn print_ndarray_body( + f: &mut Formatter<'_>, + shape: &[usize], + cell: impl Fn(&[usize]) -> T, +) -> fmt::Result { + match shape.len() { + 0 => writeln!(f, " {}", format_float(cell(&[]))), + 1 => { + let headers = [String::from("value")]; + print_float_grid(f, &headers, shape[0], |r, _| cell(&[r])) + } + 2 => { + let headers: Vec = (0..shape[1]).map(|c| format!("col_{c}")).collect(); + print_float_grid(f, &headers, shape[0], |r, c| cell(&[r, c])) + } + _ => { + // The trailing axes flatten into columns, so each column header + // names the coordinate its values carry on those axes. + let outer = &shape[1..]; + let n_outer: usize = outer.iter().product(); + let headers: Vec = (0..n_outer) + .map(|j| { + let mut remaining = j; + let mut coords = Vec::with_capacity(outer.len()); + for &size in outer { + coords.push((remaining % size).to_string()); + remaining /= size; + } + format!("({})", coords.join(",")) + }) + .collect(); + print_float_grid(f, &headers, shape[0], |r, j| { + let mut index = Vec::with_capacity(shape.len()); + index.push(r); + let mut remaining = j; + for &size in &shape[1..] { + index.push(remaining % size); + remaining /= size; + } + cell(&index) + }) + } + } +} + #[cfg(feature = "datetime")] pub(crate) fn format_datetime_value( arr: &DatetimeArray,