vortex_array/array/display/mod.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4mod tree;
5
6use std::fmt::Display;
7
8use itertools::Itertools as _;
9use tree::TreeDisplayWrapper;
10
11use crate::Array;
12
13/// Describe how to convert an array to a string.
14///
15/// See also:
16/// [Array::display_as](../trait.Array.html#method.display_as)
17/// and [DisplayArrayAs].
18pub enum DisplayOptions {
19 /// Only the top-level encoding id and limited metadata: `vortex.primitive(i16, len=5)`.
20 ///
21 /// ```
22 /// # use vortex_array::display::DisplayOptions;
23 /// # use vortex_array::IntoArray;
24 /// # use vortex_buffer::buffer;
25 /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
26 /// assert_eq!(
27 /// format!("{}", array.display_as(DisplayOptions::MetadataOnly)),
28 /// "vortex.primitive(i16, len=5)",
29 /// );
30 /// ```
31 MetadataOnly,
32 /// Only the logical values of the array: `[0i16, 1i16, 2i16, 3i16, 4i16]`.
33 ///
34 /// ```
35 /// # use vortex_array::display::DisplayOptions;
36 /// # use vortex_array::IntoArray;
37 /// # use vortex_buffer::buffer;
38 /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
39 /// assert_eq!(
40 /// format!("{}", array.display_as(DisplayOptions::default())),
41 /// "[0i16, 1i16, 2i16, 3i16, 4i16]",
42 /// );
43 /// assert_eq!(
44 /// format!("{}", array.display_as(DisplayOptions::default())),
45 /// format!("{}", array.display_values()),
46 /// );
47 /// ```
48 CommaSeparatedScalars { omit_comma_after_space: bool },
49 /// The tree of encodings and all metadata but no values.
50 ///
51 /// ```
52 /// # use vortex_array::display::DisplayOptions;
53 /// # use vortex_array::IntoArray;
54 /// # use vortex_buffer::buffer;
55 /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
56 /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
57 /// metadata: EmptyMetadata
58 /// buffer (align=2): 10 B (100.00%)
59 /// ";
60 /// assert_eq!(format!("{}", array.display_as(DisplayOptions::TreeDisplay)), expected);
61 /// ```
62 TreeDisplay,
63 /// Display values in a formatted table with columns.
64 ///
65 /// For struct arrays, displays a column for each field in the struct.
66 /// For regular arrays, displays a single column with values.
67 ///
68 /// ```
69 /// # use vortex_array::display::DisplayOptions;
70 /// # use vortex_array::arrays::StructArray;
71 /// # use vortex_array::IntoArray;
72 /// # use vortex_buffer::buffer;
73 /// let s = StructArray::from_fields(&[
74 /// ("x", buffer![1, 2].into_array()),
75 /// ("y", buffer![3, 4].into_array()),
76 /// ]).unwrap().into_array();
77 /// let expected = "
78 /// ┌──────┬──────┐
79 /// │ x │ y │
80 /// ├──────┼──────┤
81 /// │ 1i32 │ 3i32 │
82 /// ├──────┼──────┤
83 /// │ 2i32 │ 4i32 │
84 /// └──────┴──────┘".trim();
85 /// assert_eq!(format!("{}", s.display_as(DisplayOptions::TableDisplay)), expected);
86 /// ```
87 #[cfg(feature = "table-display")]
88 TableDisplay,
89}
90
91impl Default for DisplayOptions {
92 fn default() -> Self {
93 Self::CommaSeparatedScalars {
94 omit_comma_after_space: false,
95 }
96 }
97}
98
99/// A shim used to display an array as specified in the options.
100///
101/// See also:
102/// [Array::display_as](../trait.Array.html#method.display_as)
103/// and [DisplayOptions].
104pub struct DisplayArrayAs<'a>(pub &'a dyn Array, pub DisplayOptions);
105
106impl Display for DisplayArrayAs<'_> {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 self.0.fmt_as(f, &self.1)
109 }
110}
111
112/// Display the encoding and limited metadata of this array.
113///
114/// # Examples
115/// ```
116/// # use vortex_array::IntoArray;
117/// # use vortex_buffer::buffer;
118/// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
119/// assert_eq!(
120/// format!("{}", array),
121/// "vortex.primitive(i16, len=5)",
122/// );
123/// ```
124impl Display for dyn Array + '_ {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 self.fmt_as(f, &DisplayOptions::MetadataOnly)
127 }
128}
129
130impl dyn Array + '_ {
131 /// Display logical values of the array
132 ///
133 /// For example, an `i16` typed array containing the first five non-negative integers is displayed
134 /// as: `[0i16, 1i16, 2i16, 3i16, 4i16]`.
135 ///
136 /// # Examples
137 ///
138 /// ```
139 /// # use vortex_array::IntoArray;
140 /// # use vortex_buffer::buffer;
141 /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
142 /// assert_eq!(
143 /// format!("{}", array.display_values()),
144 /// "[0i16, 1i16, 2i16, 3i16, 4i16]",
145 /// )
146 /// ```
147 ///
148 /// See also:
149 /// [Array::display_as](..//trait.Array.html#method.display_as),
150 /// [DisplayArrayAs], and [DisplayOptions].
151 pub fn display_values(&self) -> impl Display {
152 DisplayArrayAs(
153 self,
154 DisplayOptions::CommaSeparatedScalars {
155 omit_comma_after_space: false,
156 },
157 )
158 }
159
160 /// Display the array as specified by the options.
161 ///
162 /// See [DisplayOptions] for examples.
163 pub fn display_as(&self, options: DisplayOptions) -> impl Display {
164 DisplayArrayAs(self, options)
165 }
166
167 /// Display the tree of encodings of this array as an indented lists.
168 ///
169 /// While some metadata (such as length, bytes and validity-rate) are included, the logical
170 /// values of the array are not displayed. To view the logical values see
171 /// [Array::display_as](../trait.Array.html#method.display_as)
172 /// and [DisplayOptions].
173 ///
174 /// # Examples
175 /// ```
176 /// # use vortex_array::display::DisplayOptions;
177 /// # use vortex_array::IntoArray;
178 /// # use vortex_buffer::buffer;
179 /// let array = buffer![0_i16, 1, 2, 3, 4].into_array();
180 /// let expected = "root: vortex.primitive(i16, len=5) nbytes=10 B (100.00%)
181 /// metadata: EmptyMetadata
182 /// buffer (align=2): 10 B (100.00%)
183 /// ";
184 /// assert_eq!(format!("{}", array.display_tree()), expected);
185 /// ```
186 pub fn display_tree(&self) -> impl Display {
187 DisplayArrayAs(self, DisplayOptions::TreeDisplay)
188 }
189
190 /// Display the array as a formatted table.
191 ///
192 /// For struct arrays, displays a column for each field in the struct.
193 /// For regular arrays, displays a single column with values.
194 ///
195 /// # Examples
196 /// ```
197 /// # #[cfg(feature = "table-display")]
198 /// # {
199 /// # use vortex_array::arrays::StructArray;
200 /// # use vortex_array::IntoArray;
201 /// # use vortex_buffer::buffer;
202 /// let s = StructArray::from_fields(&[
203 /// ("x", buffer![1, 2].into_array()),
204 /// ("y", buffer![3, 4].into_array()),
205 /// ]).unwrap().into_array();
206 /// let expected = "
207 /// ┌──────┬──────┐
208 /// │ x │ y │
209 /// ├──────┼──────┤
210 /// │ 1i32 │ 3i32 │
211 /// ├──────┼──────┤
212 /// │ 2i32 │ 4i32 │
213 /// └──────┴──────┘".trim();
214 /// assert_eq!(format!("{}", s.display_table()), expected);
215 /// # }
216 /// ```
217 #[cfg(feature = "table-display")]
218 pub fn display_table(&self) -> impl Display {
219 DisplayArrayAs(self, DisplayOptions::TableDisplay)
220 }
221
222 fn fmt_as(&self, f: &mut std::fmt::Formatter, options: &DisplayOptions) -> std::fmt::Result {
223 match options {
224 DisplayOptions::MetadataOnly => {
225 write!(
226 f,
227 "{}({}, len={})",
228 self.encoding_id(),
229 self.dtype(),
230 self.len()
231 )
232 }
233 DisplayOptions::CommaSeparatedScalars {
234 omit_comma_after_space,
235 } => {
236 write!(f, "[")?;
237 let sep = if *omit_comma_after_space { "," } else { ", " };
238 write!(
239 f,
240 "{}",
241 (0..self.len()).map(|i| self.scalar_at(i)).format(sep)
242 )?;
243 write!(f, "]")
244 }
245 DisplayOptions::TreeDisplay => write!(f, "{}", TreeDisplayWrapper(self.to_array())),
246 #[cfg(feature = "table-display")]
247 DisplayOptions::TableDisplay => {
248 use vortex_dtype::DType;
249 use vortex_error::VortexExpect as _;
250
251 use crate::canonical::ToCanonical;
252
253 let mut builder = tabled::builder::Builder::default();
254
255 // Special logic for struct arrays.
256 let DType::Struct(sf, _) = self.dtype() else {
257 // For non-struct arrays, simply display a single column table without header.
258 for row_idx in 0..self.len() {
259 let value = self.scalar_at(row_idx);
260 builder.push_record([value.to_string()]);
261 }
262
263 let mut table = builder.build();
264 table.with(tabled::settings::Style::modern());
265
266 return write!(f, "{table}");
267 };
268
269 let struct_ = self.to_struct().vortex_expect("struct array");
270 builder.push_record(sf.names().iter().map(|name| name.to_string()));
271
272 for row_idx in 0..self.len() {
273 if !self.is_valid(row_idx).vortex_expect("index in bounds") {
274 let null_row = vec!["null".to_string(); sf.names().len()];
275 builder.push_record(null_row);
276 } else {
277 let mut row = Vec::new();
278 for field_array in struct_.fields() {
279 let value = field_array.scalar_at(row_idx);
280 row.push(value.to_string());
281 }
282 builder.push_record(row);
283 }
284 }
285
286 let mut table = builder.build();
287 table.with(tabled::settings::Style::modern());
288
289 // Center headers
290 for col_idx in 0..sf.names().len() {
291 table.modify((0, col_idx), tabled::settings::Alignment::center());
292 }
293
294 for row_idx in 0..self.len() {
295 if !self.is_valid(row_idx).vortex_expect("index is in bounds") {
296 table.modify(
297 (1 + row_idx, 0),
298 tabled::settings::Span::column(sf.names().len() as isize),
299 );
300 table.modify((1 + row_idx, 0), tabled::settings::Alignment::center());
301 }
302 }
303
304 write!(f, "{table}")
305 }
306 }
307 }
308}
309
310#[cfg(test)]
311mod test {
312 use vortex_buffer::{Buffer, buffer};
313 use vortex_dtype::FieldNames;
314
315 use crate::IntoArray as _;
316 use crate::arrays::{BoolArray, ListArray, StructArray};
317 use crate::validity::Validity;
318
319 #[test]
320 fn test_primitive() {
321 let x = Buffer::<u32>::empty().into_array();
322 assert_eq!(x.display_values().to_string(), "[]");
323
324 let x = buffer![1].into_array();
325 assert_eq!(x.display_values().to_string(), "[1i32]");
326
327 let x = buffer![1, 2, 3, 4].into_array();
328 assert_eq!(x.display_values().to_string(), "[1i32, 2i32, 3i32, 4i32]");
329 }
330
331 #[test]
332 fn test_empty_struct() {
333 let s = StructArray::try_new(
334 FieldNames::from(vec![]),
335 vec![],
336 3,
337 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
338 )
339 .unwrap()
340 .into_array();
341 assert_eq!(s.display_values().to_string(), "[{}, null, {}]");
342 }
343
344 #[test]
345 fn test_simple_struct() {
346 let s = StructArray::from_fields(&[
347 ("x", buffer![1, 2, 3, 4].into_array()),
348 ("y", buffer![-1, -2, -3, -4].into_array()),
349 ])
350 .unwrap()
351 .into_array();
352 assert_eq!(
353 s.display_values().to_string(),
354 "[{x: 1i32, y: -1i32}, {x: 2i32, y: -2i32}, {x: 3i32, y: -3i32}, {x: 4i32, y: -4i32}]"
355 );
356 }
357
358 #[test]
359 fn test_list() {
360 let x = ListArray::try_new(
361 buffer![1, 2, 3, 4].into_array(),
362 buffer![0, 0, 1, 1, 2, 4].into_array(),
363 Validity::Array(BoolArray::from_iter([true, true, false, true, true]).into_array()),
364 )
365 .unwrap()
366 .into_array();
367 assert_eq!(
368 x.display_values().to_string(),
369 "[[], [1i32], null, [2i32], [3i32, 4i32]]"
370 );
371 }
372
373 #[test]
374 #[cfg(feature = "table-display")]
375 fn test_table_display_primitive() {
376 use crate::display::DisplayOptions;
377
378 let array = buffer![1, 2, 3, 4].into_array();
379 let table_display = array.display_as(DisplayOptions::TableDisplay);
380 assert_eq!(
381 table_display.to_string(),
382 r"
383┌──────┐
384│ 1i32 │
385├──────┤
386│ 2i32 │
387├──────┤
388│ 3i32 │
389├──────┤
390│ 4i32 │
391└──────┘"
392 .trim()
393 );
394 }
395
396 #[test]
397 #[cfg(feature = "table-display")]
398 fn test_table_display() {
399 use crate::display::DisplayOptions;
400
401 let array = crate::arrays::PrimitiveArray::from_option_iter(vec![
402 Some(-1),
403 Some(-2),
404 Some(-3),
405 None,
406 ])
407 .into_array();
408
409 let struct_ = StructArray::try_from_iter_with_validity(
410 [("x", buffer![1, 2, 3, 4].into_array()), ("y", array)],
411 Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
412 )
413 .unwrap()
414 .into_array();
415
416 let table_display = struct_.display_as(DisplayOptions::TableDisplay);
417 assert_eq!(
418 table_display.to_string(),
419 r"
420┌──────┬───────┐
421│ x │ y │
422├──────┼───────┤
423│ 1i32 │ -1i32 │
424├──────┼───────┤
425│ null │
426├──────┼───────┤
427│ 3i32 │ -3i32 │
428├──────┼───────┤
429│ 4i32 │ null │
430└──────┴───────┘"
431 .trim()
432 );
433 }
434}