Skip to main content

diskann_benchmark_runner/utils/
fmt.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::{
7    collections::HashMap,
8    fmt::{Display, Write},
9};
10
11/// A 2-d table for formatting properly spaced values in a table.
12pub struct Table {
13    // The number of columns is implicitly described by the number of entries in `header`.
14    header: Box<[Box<dyn Display>]>,
15    body: HashMap<(usize, usize), Box<dyn Display>>,
16    nrows: usize,
17}
18
19impl Table {
20    pub fn new<I>(header: I, nrows: usize) -> Self
21    where
22        I: IntoIterator<Item: Display + 'static>,
23    {
24        fn as_dyn_display<T: Display + 'static>(x: T) -> Box<dyn Display> {
25            Box::new(x)
26        }
27
28        let header: Box<[_]> = header.into_iter().map(as_dyn_display).collect();
29        Self {
30            header,
31            body: HashMap::new(),
32            nrows,
33        }
34    }
35
36    pub fn nrows(&self) -> usize {
37        self.nrows
38    }
39
40    pub fn ncols(&self) -> usize {
41        self.header.len()
42    }
43
44    pub fn insert<T>(&mut self, item: T, row: usize, col: usize) -> bool
45    where
46        T: Display + 'static,
47    {
48        self.check_bounds(row, col);
49        self.body.insert((row, col), Box::new(item)).is_some()
50    }
51
52    pub fn get(&self, row: usize, col: usize) -> Option<&dyn Display> {
53        self.check_bounds(row, col);
54        self.body.get(&(row, col)).map(|x| &**x)
55    }
56
57    pub fn row(&mut self, row: usize) -> Row<'_> {
58        self.check_bounds(row, 0);
59        Row::new(self, row)
60    }
61
62    #[expect(clippy::panic, reason = "table interfaces are bounds checked")]
63    fn check_bounds(&self, row: usize, col: usize) {
64        if row >= self.nrows() {
65            panic!("row {} is out of bounds (max {})", row, self.nrows());
66        }
67        if col >= self.ncols() {
68            panic!("col {} is out of bounds (max {})", col, self.ncols());
69        }
70    }
71}
72
73pub struct Row<'a> {
74    table: &'a mut Table,
75    row: usize,
76}
77
78impl<'a> Row<'a> {
79    // A **private** constructor assuming that `row` is inbounds.
80    fn new(table: &'a mut Table, row: usize) -> Self {
81        Self { table, row }
82    }
83
84    /// Insert a value into the specified column of this row.
85    pub fn insert<T>(&mut self, item: T, col: usize) -> bool
86    where
87        T: Display + 'static,
88    {
89        self.table.insert(item, self.row, col)
90    }
91}
92
93impl Display for Table {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        const SEP: &str = ",   ";
96
97        // Compute the maximum width of each column.
98        struct Count(usize);
99
100        impl Write for Count {
101            fn write_str(&mut self, s: &str) -> std::fmt::Result {
102                self.0 += s.len();
103                Ok(())
104            }
105        }
106
107        fn formatted_size<T>(x: &T) -> usize
108        where
109            T: Display + ?Sized,
110        {
111            let mut buf = Count(0);
112            match write!(&mut buf, "{}", x) {
113                // Return the number of bytes "written",
114                Ok(()) => buf.0,
115                Err(_) => 0,
116            }
117        }
118
119        let mut widths: Vec<usize> = self.header.iter().map(formatted_size).collect();
120        for row in 0..self.nrows() {
121            for (col, width) in widths.iter_mut().enumerate() {
122                if let Some(v) = self.body.get(&(row, col)) {
123                    *width = (*width).max(formatted_size(v))
124                }
125            }
126        }
127
128        let header_width: usize = widths.iter().sum::<usize>() + (widths.len() - 1) * SEP.len();
129
130        let mut buf = String::new();
131        // Print the header.
132        std::iter::zip(widths.iter(), self.header.iter())
133            .enumerate()
134            .try_for_each(|(col, (width, head))| {
135                buf.clear();
136                write!(buf, "{}", head)?;
137                write!(f, "{:>width$}", buf)?;
138                if col + 1 != self.ncols() {
139                    write!(f, "{}", SEP)?;
140                }
141                Ok(())
142            })?;
143
144        // Banner
145        write!(f, "\n{:=>header_width$}\n", "")?;
146
147        // Write out each row.
148        for row in 0..self.nrows() {
149            for (col, width) in widths.iter_mut().enumerate() {
150                match self.body.get(&(row, col)) {
151                    Some(v) => {
152                        buf.clear();
153                        write!(buf, "{}", v)?;
154                        write!(f, "{:>width$}", buf)?;
155                    }
156                    None => write!(f, "{:>width$}", "")?,
157                }
158                if col + 1 != self.ncols() {
159                    write!(f, "{}", SEP)?;
160                } else {
161                    writeln!(f)?;
162                }
163            }
164        }
165        Ok(())
166    }
167}
168
169////////////
170// Banner //
171////////////
172
173pub(crate) struct Banner<'a>(&'a str);
174
175impl<'a> Banner<'a> {
176    pub(crate) fn new(message: &'a str) -> Self {
177        Self(message)
178    }
179}
180
181impl std::fmt::Display for Banner<'_> {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        let st = format!("# {} #", self.0);
184        let len = st.len();
185        writeln!(f, "{:#>len$}", "")?;
186        writeln!(f, "{}", st)?;
187        writeln!(f, "{:#>len$}", "")?;
188        Ok(())
189    }
190}
191
192///////////
193// Tests //
194///////////
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_banner() {
202        let b = Banner::new("hello world");
203        let s = b.to_string();
204
205        let expected = "###############\n\
206                        # hello world #\n\
207                        ###############\n";
208
209        assert_eq!(s, expected);
210
211        let b = Banner::new("");
212        let s = b.to_string();
213
214        let expected = "####\n\
215                        #  #\n\
216                        ####\n";
217
218        assert_eq!(s, expected);
219
220        let b = Banner::new("foo");
221        let s = b.to_string();
222
223        let expected = "#######\n\
224                        # foo #\n\
225                        #######\n";
226
227        assert_eq!(s, expected);
228    }
229
230    #[test]
231    fn test_format() {
232        // One column
233        {
234            let headers = ["h 0"];
235            let mut table = Table::new(headers, 3);
236            table.insert("a", 0, 0);
237            table.insert("hello world", 1, 0);
238            table.insert(62, 2, 0);
239
240            let s = table.to_string();
241            let expected = r#"
242        h 0
243===========
244          a
245hello world
246         62
247"#;
248            assert_eq!(s, expected.strip_prefix('\n').unwrap());
249        }
250
251        // Two columns
252        {
253            let headers = ["a really really long header", "h1"];
254            let mut table = Table::new(headers, 3);
255            table.insert("a", 0, 0);
256            table.insert("b", 0, 1);
257
258            table.insert("hello world", 1, 0);
259            table.insert("hello world version 2", 1, 1);
260
261            table.insert(7, 2, 0);
262            table.insert("bar", 2, 1);
263
264            let s = table.to_string();
265            let expected = r#"
266a really really long header,                      h1
267====================================================
268                          a,                       b
269                hello world,   hello world version 2
270                          7,                     bar
271"#;
272            assert_eq!(s, expected.strip_prefix('\n').unwrap());
273        }
274    }
275
276    #[test]
277    fn test_row_api() {
278        let mut table = Table::new(["a", "b", "c"], 2);
279        let mut row = table.row(0);
280        row.insert(1, 0);
281        row.insert("long", 1);
282        row.insert("s", 2);
283
284        let mut row = table.row(1);
285        row.insert("string", 0);
286        row.insert(2, 1);
287        row.insert(3, 2);
288
289        let s = table.to_string();
290
291        let expected = r#"
292     a,      b,   c
293===================
294     1,   long,   s
295string,      2,   3
296"#;
297        assert_eq!(s, expected.strip_prefix('\n').unwrap());
298    }
299
300    #[test]
301    fn missing_values() {
302        let mut table = Table::new(["a", "loong", "c"], 1);
303        let mut row = table.row(0);
304        row.insert("string", 0);
305        row.insert("string", 2);
306
307        let s = table.to_string();
308        let expected = r#"
309     a,   loong,        c
310=========================
311string,        ,   string
312"#;
313        assert_eq!(s, expected.strip_prefix('\n').unwrap());
314    }
315
316    #[test]
317    #[should_panic(expected = "row 3 is out of bounds (max 2)")]
318    fn test_panic_row() {
319        let mut table = Table::new([1, 2, 3], 2);
320        let _ = table.row(3);
321    }
322
323    #[test]
324    #[should_panic(expected = "col 3 is out of bounds (max 2)")]
325    fn test_panic_col() {
326        let mut table = Table::new([1, 2], 1);
327        let mut row = table.row(0);
328        row.insert(1, 3);
329    }
330}