Skip to content

Commit 61b4835

Browse files
Merge pull request #606 from andrei-papou/smart-debug-formatting
Implemented a function for smarter debug formatting.
2 parents 44bf8b8 + 72e05d7 commit 61b4835

File tree

1 file changed

+261
-68
lines changed

1 file changed

+261
-68
lines changed

src/arrayformat.rs

+261-68
Original file line numberDiff line numberDiff line change
@@ -8,77 +8,115 @@
88
use std::fmt;
99
use super::{
1010
ArrayBase,
11+
Axis,
1112
Data,
1213
Dimension,
1314
NdProducer,
15+
Ix
1416
};
15-
use crate::dimension::IntoDimension;
16-
17-
fn format_array<A, S, D, F>(view: &ArrayBase<S, D>, f: &mut fmt::Formatter,
18-
mut format: F)
19-
-> fmt::Result
20-
where F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result,
21-
D: Dimension,
22-
S: Data<Elem=A>,
17+
use crate::aliases::Ix1;
18+
19+
const PRINT_ELEMENTS_LIMIT: Ix = 3;
20+
21+
fn format_1d_array<A, S, F>(
22+
view: &ArrayBase<S, Ix1>,
23+
f: &mut fmt::Formatter,
24+
mut format: F,
25+
limit: Ix) -> fmt::Result
26+
where
27+
F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result,
28+
S: Data<Elem=A>,
2329
{
24-
let ndim = view.dim.slice().len();
25-
/* private nowadays
26-
if ndim > 0 && f.width.is_none() {
27-
f.width = Some(4)
28-
}
29-
*/
30-
// None will be an empty iter.
31-
let mut last_index = match view.dim.first_index() {
32-
None => view.dim.clone(),
33-
Some(ix) => ix,
34-
};
35-
for _ in 0..ndim {
36-
write!(f, "[")?;
37-
}
38-
let mut first = true;
39-
// Simply use the indexed iterator, and take the index wraparounds
40-
// as cues for when to add []'s and how many to add.
41-
for (index, elt) in view.indexed_iter() {
42-
let index = index.into_dimension();
43-
let take_n = if ndim == 0 { 1 } else { ndim - 1 };
44-
let mut update_index = false;
45-
for (i, (a, b)) in index.slice()
46-
.iter()
47-
.take(take_n)
48-
.zip(last_index.slice().iter())
49-
.enumerate() {
50-
if a != b {
51-
// New row.
52-
// # of ['s needed
53-
let n = ndim - i - 1;
54-
for _ in 0..n {
55-
write!(f, "]")?;
56-
}
57-
write!(f, ",")?;
58-
write!(f, "\n")?;
59-
for _ in 0..ndim - n {
60-
write!(f, " ")?;
61-
}
62-
for _ in 0..n {
63-
write!(f, "[")?;
30+
let to_be_printed = to_be_printed(view.len(), limit);
31+
32+
let n_to_be_printed = to_be_printed.len();
33+
34+
write!(f, "[")?;
35+
for (j, index) in to_be_printed.into_iter().enumerate() {
36+
match index {
37+
PrintableCell::ElementIndex(i) => {
38+
format(&view[i], f)?;
39+
if j != n_to_be_printed - 1 {
40+
write!(f, ", ")?;
6441
}
65-
first = true;
66-
update_index = true;
67-
break;
68-
}
69-
}
70-
if !first {
71-
write!(f, ", ")?;
42+
},
43+
PrintableCell::Ellipses => write!(f, "..., ")?,
7244
}
73-
first = false;
74-
format(elt, f)?;
45+
}
46+
write!(f, "]")?;
47+
Ok(())
48+
}
7549

76-
if update_index {
77-
last_index = index;
78-
}
50+
enum PrintableCell {
51+
ElementIndex(usize),
52+
Ellipses,
53+
}
54+
55+
// Returns what indexes should be printed for a certain axis.
56+
// If the axis is longer than 2 * limit, a `Ellipses` is inserted
57+
// where indexes are being omitted.
58+
fn to_be_printed(length: usize, limit: usize) -> Vec<PrintableCell> {
59+
if length <= 2 * limit {
60+
(0..length).map(|x| PrintableCell::ElementIndex(x)).collect()
61+
} else {
62+
let mut v: Vec<PrintableCell> = (0..limit).map(|x| PrintableCell::ElementIndex(x)).collect();
63+
v.push(PrintableCell::Ellipses);
64+
v.extend((length-limit..length).map(|x| PrintableCell::ElementIndex(x)));
65+
v
7966
}
80-
for _ in 0..ndim {
81-
write!(f, "]")?;
67+
}
68+
69+
fn format_array<A, S, D, F>(
70+
view: &ArrayBase<S, D>,
71+
f: &mut fmt::Formatter,
72+
mut format: F,
73+
limit: Ix) -> fmt::Result
74+
where
75+
F: FnMut(&A, &mut fmt::Formatter) -> fmt::Result + Clone,
76+
D: Dimension,
77+
S: Data<Elem=A>,
78+
{
79+
// If any of the axes has 0 length, we return the same empty array representation
80+
// e.g. [[]] for 2-d arrays
81+
if view.shape().iter().any(|&x| x == 0) {
82+
write!(f, "{}{}", "[".repeat(view.ndim()), "]".repeat(view.ndim()))?;
83+
return Ok(())
84+
}
85+
match view.shape() {
86+
// If it's 0 dimensional, we just print out the scalar
87+
[] => format(view.iter().next().unwrap(), f)?,
88+
// We delegate 1-dimensional arrays to a specialized function
89+
[_] => format_1d_array(&view.view().into_dimensionality::<Ix1>().unwrap(), f, format, limit)?,
90+
// For n-dimensional arrays, we proceed recursively
91+
shape => {
92+
// Cast into a dynamically dimensioned view
93+
// This is required to be able to use `index_axis`
94+
let view = view.view().into_dyn();
95+
// We start by checking what indexes from the first axis should be printed
96+
// We put a `None` in the middle if we are omitting elements
97+
let to_be_printed = to_be_printed(shape[0], limit);
98+
99+
let n_to_be_printed = to_be_printed.len();
100+
101+
write!(f, "[")?;
102+
for (j, index) in to_be_printed.into_iter().enumerate() {
103+
match index {
104+
PrintableCell::ElementIndex(i) => {
105+
// Proceed recursively with the (n-1)-dimensional slice
106+
format_array(
107+
&view.index_axis(Axis(0), i), f, format.clone(), limit
108+
)?;
109+
// We need to add a separator after each slice,
110+
// apart from the last one
111+
if j != n_to_be_printed - 1 {
112+
write!(f, ",\n ")?
113+
}
114+
},
115+
PrintableCell::Ellipses => write!(f, "...,\n ")?
116+
}
117+
}
118+
write!(f, "]")?;
119+
}
82120
}
83121
Ok(())
84122
}
@@ -92,7 +130,7 @@ impl<'a, A: fmt::Display, S, D: Dimension> fmt::Display for ArrayBase<S, D>
92130
where S: Data<Elem=A>,
93131
{
94132
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95-
format_array(self, f, <_>::fmt)
133+
format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)
96134
}
97135
}
98136

@@ -105,7 +143,7 @@ impl<'a, A: fmt::Debug, S, D: Dimension> fmt::Debug for ArrayBase<S, D>
105143
{
106144
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107145
// Add extra information for Debug
108-
format_array(self, f, <_>::fmt)?;
146+
format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)?;
109147
write!(f, " shape={:?}, strides={:?}, layout={:?}",
110148
self.shape(), self.strides(), layout=self.view().layout())?;
111149
match D::NDIM {
@@ -124,7 +162,7 @@ impl<'a, A: fmt::LowerExp, S, D: Dimension> fmt::LowerExp for ArrayBase<S, D>
124162
where S: Data<Elem=A>,
125163
{
126164
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127-
format_array(self, f, <_>::fmt)
165+
format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)
128166
}
129167
}
130168

@@ -136,7 +174,7 @@ impl<'a, A: fmt::UpperExp, S, D: Dimension> fmt::UpperExp for ArrayBase<S, D>
136174
where S: Data<Elem=A>,
137175
{
138176
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139-
format_array(self, f, <_>::fmt)
177+
format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)
140178
}
141179
}
142180
/// Format the array using `LowerHex` and apply the formatting parameters used
@@ -147,7 +185,7 @@ impl<'a, A: fmt::LowerHex, S, D: Dimension> fmt::LowerHex for ArrayBase<S, D>
147185
where S: Data<Elem=A>,
148186
{
149187
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150-
format_array(self, f, <_>::fmt)
188+
format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)
151189
}
152190
}
153191

@@ -159,6 +197,161 @@ impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase<S, D>
159197
where S: Data<Elem=A>,
160198
{
161199
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162-
format_array(self, f, <_>::fmt)
200+
format_array(self, f, <_>::fmt, PRINT_ELEMENTS_LIMIT)
201+
}
202+
}
203+
204+
#[cfg(test)]
205+
mod formatting_with_omit {
206+
use crate::prelude::*;
207+
use super::*;
208+
209+
fn print_output_diff(expected: &str, actual: &str) {
210+
println!("Expected output:\n{}\nActual output:\n{}", expected, actual);
211+
}
212+
213+
#[test]
214+
fn empty_arrays() {
215+
let a: Array2<u32> = arr2(&[[], []]);
216+
let actual_output = format!("{}", a);
217+
let expected_output = String::from("[[]]");
218+
print_output_diff(&expected_output, &actual_output);
219+
assert_eq!(expected_output, actual_output);
220+
}
221+
222+
#[test]
223+
fn zero_length_axes() {
224+
let a = Array3::<f32>::zeros((3, 0, 4));
225+
let actual_output = format!("{}", a);
226+
let expected_output = String::from("[[[]]]");
227+
print_output_diff(&expected_output, &actual_output);
228+
assert_eq!(expected_output, actual_output);
229+
}
230+
231+
#[test]
232+
fn dim_0() {
233+
let element = 12;
234+
let a = arr0(element);
235+
let actual_output = format!("{}", a);
236+
let expected_output = format!("{}", element);
237+
print_output_diff(&expected_output, &actual_output);
238+
assert_eq!(expected_output, actual_output);
239+
}
240+
241+
#[test]
242+
fn dim_1() {
243+
let overflow: usize = 5;
244+
let a = Array1::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, ), 1);
245+
let mut expected_output = String::from("[");
246+
a.iter()
247+
.take(PRINT_ELEMENTS_LIMIT)
248+
.for_each(|elem| { expected_output.push_str(format!("{}, ", elem).as_str()) });
249+
expected_output.push_str("...");
250+
a.iter()
251+
.skip(PRINT_ELEMENTS_LIMIT + overflow)
252+
.for_each(|elem| { expected_output.push_str(format!(", {}", elem).as_str()) });
253+
expected_output.push(']');
254+
let actual_output = format!("{}", a);
255+
256+
print_output_diff(&expected_output, &actual_output);
257+
assert_eq!(actual_output, expected_output);
258+
}
259+
260+
#[test]
261+
fn dim_2_last_axis_overflow() {
262+
let overflow: usize = 3;
263+
let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1);
264+
let mut expected_output = String::from("[");
265+
266+
for i in 0..PRINT_ELEMENTS_LIMIT {
267+
expected_output.push_str(format!("[{}", a[(i, 0)]).as_str());
268+
for j in 1..PRINT_ELEMENTS_LIMIT {
269+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
270+
}
271+
expected_output.push_str(", ...");
272+
for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow {
273+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
274+
}
275+
expected_output.push_str(if i < PRINT_ELEMENTS_LIMIT - 1 { "],\n " } else { "]" });
276+
}
277+
expected_output.push(']');
278+
let actual_output = format!("{}", a);
279+
280+
print_output_diff(&expected_output, &actual_output);
281+
assert_eq!(actual_output, expected_output);
282+
}
283+
284+
#[test]
285+
fn dim_2_non_last_axis_overflow() {
286+
let overflow: usize = 5;
287+
let a = Array2::from_elem((PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT), 1);
288+
let mut expected_output = String::from("[");
289+
290+
for i in 0..PRINT_ELEMENTS_LIMIT {
291+
expected_output.push_str(format!("[{}", a[(i, 0)]).as_str());
292+
for j in 1..PRINT_ELEMENTS_LIMIT {
293+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
294+
}
295+
expected_output.push_str("],\n ");
296+
}
297+
expected_output.push_str("...,\n ");
298+
for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow {
299+
expected_output.push_str(format!("[{}", a[(i, 0)]).as_str());
300+
for j in 1..PRINT_ELEMENTS_LIMIT {
301+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
302+
}
303+
expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 {
304+
"]"
305+
} else {
306+
"],\n "
307+
});
308+
}
309+
expected_output.push(']');
310+
let actual_output = format!("{}", a);
311+
312+
print_output_diff(&expected_output, &actual_output);
313+
assert_eq!(actual_output, expected_output);
314+
}
315+
316+
#[test]
317+
fn dim_2_multi_directional_overflow() {
318+
let overflow: usize = 5;
319+
let a = Array2::from_elem(
320+
(PRINT_ELEMENTS_LIMIT * 2 + overflow, PRINT_ELEMENTS_LIMIT * 2 + overflow), 1
321+
);
322+
let mut expected_output = String::from("[");
323+
324+
for i in 0..PRINT_ELEMENTS_LIMIT {
325+
expected_output.push_str(format!("[{}", a[(i, 0)]).as_str());
326+
for j in 1..PRINT_ELEMENTS_LIMIT {
327+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
328+
}
329+
expected_output.push_str(", ...");
330+
for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow {
331+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
332+
}
333+
expected_output.push_str("],\n ");
334+
}
335+
expected_output.push_str("...,\n ");
336+
for i in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow {
337+
expected_output.push_str(format!("[{}", a[(i, 0)]).as_str());
338+
for j in 1..PRINT_ELEMENTS_LIMIT {
339+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
340+
}
341+
expected_output.push_str(", ...");
342+
for j in PRINT_ELEMENTS_LIMIT + overflow..PRINT_ELEMENTS_LIMIT * 2 + overflow {
343+
expected_output.push_str(format!(", {}", a[(i, j)]).as_str());
344+
}
345+
expected_output.push_str(if i == PRINT_ELEMENTS_LIMIT * 2 + overflow - 1 {
346+
"]"
347+
} else {
348+
"],\n "
349+
});
350+
}
351+
expected_output.push(']');
352+
let actual_output = format!("{}", a);
353+
354+
print_output_diff(&expected_output, &actual_output);
355+
assert_eq!(actual_output, expected_output);
163356
}
164357
}

0 commit comments

Comments
 (0)