diff --git a/src/arrayformat.rs b/src/arrayformat.rs index 3ab584c30..42575307c 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -9,55 +9,68 @@ use super::{ArrayBase, Axis, Data, Dimension, Ix, NdProducer}; use crate::aliases::Ix1; use std::fmt; -const PRINT_ELEMENTS_LIMIT: Ix = 3; +/// Default maximum axis length before overflowing with an ellipsis. +const AXIS_LEN_LIMIT: Ix = 6; -fn format_1d_array( - view: &ArrayBase, +/// The string used as an ellipsis. +const ELLIPSIS: &str = "..."; + +/// Returns the axis length limit based on whether or not the alternate (`#`) +/// flag was specified on the formatter. +fn axis_len_limit(f: &mut fmt::Formatter<'_>) -> usize { + if f.alternate() { + std::usize::MAX + } else { + AXIS_LEN_LIMIT + } +} + +/// Formats the contents of a list of items, using an ellipsis to indicate when +/// the `length` of the list is greater than `limit`. +/// +/// # Parameters +/// +/// * `f`: The formatter. +/// * `length`: The length of the list. +/// * `limit`: The maximum number of items before overflow. +/// * `separator`: Separator to write between items. +/// * `ellipsis`: Ellipsis for indicating overflow. +/// * `fmt_elem`: A function that formats an element in the list, given the +/// formatter and the index of the item in the list. +fn format_with_overflow( f: &mut fmt::Formatter<'_>, - mut format: F, - limit: Ix, + length: usize, + limit: usize, + separator: &str, + ellipsis: &str, + mut fmt_elem: F, ) -> fmt::Result where - F: FnMut(&A, &mut fmt::Formatter<'_>) -> fmt::Result, - S: Data, + F: FnMut(&mut fmt::Formatter<'_>, usize) -> fmt::Result, { - let to_be_printed = to_be_printed(view.len(), limit); - - let n_to_be_printed = to_be_printed.len(); - - write!(f, "[")?; - for (j, index) in to_be_printed.into_iter().enumerate() { - match index { - PrintableCell::ElementIndex(i) => { - format(&view[i], f)?; - if j != n_to_be_printed - 1 { - write!(f, ", ")?; - } - } - PrintableCell::Ellipses => write!(f, "..., ")?, - } - } - write!(f, "]")?; - Ok(()) -} - -enum PrintableCell { - ElementIndex(usize), - Ellipses, -} - -// Returns what indexes should be printed for a certain axis. -// If the axis is longer than 2 * limit, a `Ellipses` is inserted -// where indexes are being omitted. -fn to_be_printed(length: usize, limit: usize) -> Vec { - if length <= 2 * limit { - (0..length).map(PrintableCell::ElementIndex).collect() + if length == 0 { + // no-op + } else if length <= limit { + fmt_elem(f, 0)?; + (1..length).try_for_each(|i| { + f.write_str(separator)?; + fmt_elem(f, i) + })?; } else { - let mut v: Vec = (0..limit).map(PrintableCell::ElementIndex).collect(); - v.push(PrintableCell::Ellipses); - v.extend((length - limit..length).map(PrintableCell::ElementIndex)); - v + let edge = limit / 2; + fmt_elem(f, 0)?; + (1..edge).try_for_each(|i| { + f.write_str(separator)?; + fmt_elem(f, i) + })?; + f.write_str(separator)?; + f.write_str(ellipsis)?; + (length - edge..length).try_for_each(|i| { + f.write_str(separator)?; + fmt_elem(f, i) + })?; } + Ok(()) } fn format_array( @@ -80,54 +93,37 @@ where } match view.shape() { // If it's 0 dimensional, we just print out the scalar - [] => format(view.iter().next().unwrap(), f)?, - // We delegate 1-dimensional arrays to a specialized function - [_] => format_1d_array( - &view.view().into_dimensionality::().unwrap(), - f, - format, - limit, - )?, + &[] => format(view.iter().next().unwrap(), f)?, + // We handle 1-D arrays as a special case + &[len] => { + let view = view.view().into_dimensionality::().unwrap(); + f.write_str("[")?; + format_with_overflow(f, len, limit, ", ", ELLIPSIS, |f, index| { + format(&view[index], f) + })?; + f.write_str("]")?; + } // For n-dimensional arrays, we proceed recursively shape => { // Cast into a dynamically dimensioned view // This is required to be able to use `index_axis` let view = view.view().into_dyn(); - // We start by checking what indexes from the first axis should be printed - // We put a `None` in the middle if we are omitting elements - let to_be_printed = to_be_printed(shape[0], limit); - - let n_to_be_printed = to_be_printed.len(); let blank_lines = "\n".repeat(shape.len() - 2); let indent = " ".repeat(depth + 1); - - write!(f, "[")?; - for (j, index) in to_be_printed.into_iter().enumerate() { - match index { - PrintableCell::ElementIndex(i) => { - // Indent all but the first line. - if j != 0 { - write!(f, "{}", indent)?; - } - // Proceed recursively with the (n-1)-dimensional slice - format_array( - &view.index_axis(Axis(0), i), - f, - format.clone(), - limit, - depth + 1, - )?; - // We need to add a separator after each slice, - // apart from the last one - if j != n_to_be_printed - 1 { - write!(f, ",\n{}", blank_lines)? - } - } - PrintableCell::Ellipses => write!(f, "{}...,\n{}", indent, blank_lines)?, - } - } - write!(f, "]")?; + let separator = format!(",\n{}{}", blank_lines, indent); + + f.write_str("[")?; + format_with_overflow(f, shape[0], limit, &separator, ELLIPSIS, |f, index| { + format_array( + &view.index_axis(Axis(0), index), + f, + format.clone(), + limit, + depth + 1, + ) + })?; + f.write_str("]")?; } } Ok(()) @@ -143,7 +139,8 @@ where S: Data, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT, 0) + let limit = axis_len_limit(f); + format_array(self, f, <_>::fmt, limit, 0) } } @@ -156,8 +153,10 @@ where S: Data, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let limit = axis_len_limit(f); + format_array(self, f, <_>::fmt, limit, 0)?; + // Add extra information for Debug - format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT, 0)?; write!( f, ", shape={:?}, strides={:?}, layout={:?}", @@ -182,7 +181,8 @@ where S: Data, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT, 0) + let limit = axis_len_limit(f); + format_array(self, f, <_>::fmt, limit, 0) } } @@ -195,7 +195,8 @@ where S: Data, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT, 0) + let limit = axis_len_limit(f); + format_array(self, f, <_>::fmt, limit, 0) } } /// Format the array using `LowerHex` and apply the formatting parameters used @@ -207,7 +208,8 @@ where S: Data, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT, 0) + let limit = axis_len_limit(f); + format_array(self, f, <_>::fmt, limit, 0) } } @@ -220,7 +222,8 @@ where S: Data, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT, 0) + let limit = axis_len_limit(f); + format_array(self, f, <_>::fmt, limit, 0) } } @@ -264,57 +267,89 @@ mod formatting_with_omit { #[test] fn dim_1() { - let overflow: usize = 5; - let a = Array1::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow,), 1); + let overflow: usize = 2; + let a = Array1::from_elem(AXIS_LEN_LIMIT + overflow, 1); let actual = format!("{}", a); let expected = "[1, 1, 1, ..., 1, 1, 1]"; assert_str_eq(expected, &actual); } + #[test] + fn dim_1_alternate() { + let overflow: usize = 2; + let a = Array1::from_elem(AXIS_LEN_LIMIT + overflow, 1); + let actual = format!("{:#}", a); + let expected = "[1, 1, 1, 1, 1, 1, 1, 1]"; + assert_str_eq(expected, &actual); + } + #[test] fn dim_2_last_axis_overflow() { - let overflow: usize = 3; - let a = Array2::from_elem( - (PRINT_ELEMENTS_LIMIT, PRINT_ELEMENTS_LIMIT * 2 + overflow), - 1, - ); + let overflow: usize = 2; + let a = Array2::from_elem((AXIS_LEN_LIMIT, AXIS_LEN_LIMIT + overflow), 1); let actual = format!("{}", a); let expected = "\ [[1, 1, 1, ..., 1, 1, 1], + [1, 1, 1, ..., 1, 1, 1], + [1, 1, 1, ..., 1, 1, 1], + [1, 1, 1, ..., 1, 1, 1], [1, 1, 1, ..., 1, 1, 1], [1, 1, 1, ..., 1, 1, 1]]"; assert_str_eq(expected, &actual); } + #[test] + fn dim_2_last_axis_overflow_alternate() { + let overflow: usize = 2; + let a = Array2::from_elem((AXIS_LEN_LIMIT, AXIS_LEN_LIMIT + overflow), 1); + let actual = format!("{:#}", a); + let expected = "\ +[[1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1]]"; + assert_str_eq(expected, &actual); + } + #[test] fn dim_2_non_last_axis_overflow() { - let overflow: usize = 5; - let a = Array2::from_elem( - (PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT), - 1, - ); + let overflow: usize = 2; + let a = Array2::from_elem((AXIS_LEN_LIMIT + overflow, AXIS_LEN_LIMIT), 1); let actual = format!("{}", a); let expected = "\ -[[1, 1, 1], - [1, 1, 1], - [1, 1, 1], +[[1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], ..., - [1, 1, 1], - [1, 1, 1], - [1, 1, 1]]"; + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]]"; + assert_str_eq(expected, &actual); + } + + #[test] + fn dim_2_non_last_axis_overflow_alternate() { + let overflow: usize = 2; + let a = Array2::from_elem((AXIS_LEN_LIMIT + overflow, AXIS_LEN_LIMIT), 1); + let actual = format!("{:#}", a); + let expected = "\ +[[1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]]"; assert_str_eq(expected, &actual); } #[test] fn dim_2_multi_directional_overflow() { - let overflow: usize = 5; - let a = Array2::from_elem( - ( - PRINT_ELEMENTS_LIMIT * 2 + overflow, - PRINT_ELEMENTS_LIMIT * 2 + overflow, - ), - 1, - ); + let overflow: usize = 2; + let a = Array2::from_elem((AXIS_LEN_LIMIT + overflow, AXIS_LEN_LIMIT + overflow), 1); let actual = format!("{}", a); let expected = "\ [[1, 1, 1, ..., 1, 1, 1], @@ -327,6 +362,23 @@ mod formatting_with_omit { assert_str_eq(expected, &actual); } + #[test] + fn dim_2_multi_directional_overflow_alternate() { + let overflow: usize = 2; + let a = Array2::from_elem((AXIS_LEN_LIMIT + overflow, AXIS_LEN_LIMIT + overflow), 1); + let actual = format!("{:#}", a); + let expected = "\ +[[1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1]]"; + assert_str_eq(expected, &actual); + } + #[test] fn dim_3_overflow_all() { let a = Array3::from_shape_fn((20, 10, 7), |(i, j, k)| {