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}