datazoo/
bitmatrix.rs

1//! A [bitset](Bitset) with fixed-size rows.
2
3use std::{fmt, mem};
4
5use crate::{div_ceil, Bitset};
6
7/// A [bitset](Bitset) with fixed-size rows.
8///
9/// Note that only the total size is tracked in `BitMatrix` and you must provide
10/// the `width` value when calling methods on `BitMatrix`.
11#[derive(Debug, Clone)]
12pub struct BitMatrix(Bitset<Box<[u32]>>);
13impl BitMatrix {
14    /// The height this matrix would have if it had given `width`.
15    ///
16    /// Note that this might be greater than the `height` given to [`Self::new_with_size`]
17    /// due to `BitMatrix` discarding information about actual size.
18    ///
19    /// # Panics
20    /// If `Self` is not empty **and** `width` equals `0` (division by zero)
21    #[inline]
22    #[must_use]
23    pub fn height(&self, width: usize) -> usize {
24        match self.0.bit_len() {
25            0 => 0,
26            total => total / width,
27        }
28    }
29    /// Iterate over active bits in given `column`.
30    ///
31    /// # Panics
32    ///
33    /// When `width = 0` (this would otherwise mean there is an infinite
34    /// amount of columns)
35    #[inline]
36    #[must_use]
37    pub fn active_rows_in_column(&self, width: usize, x: usize) -> Column {
38        assert_ne!(width, 0);
39        Column { data: &self.0 .0, width, current_cell: x }
40    }
41    /// Iterate over the enabled bits of a single row at `y` of this `Bitmatrix`.
42    ///
43    /// Assuming the `Bitmatrix` has the provided `width`.
44    pub fn row(&self, width: usize, y: usize) -> impl Iterator<Item = usize> + '_ {
45        let start = y * width;
46        let end = (y + 1) * width;
47
48        self.0
49            .ones_in_range(start..end)
50            .map(move |i| (i as usize) - start)
51    }
52    /// Enables bit at position `bit`.
53    ///
54    /// Returns `None` and does nothing if `bit` is out of range.
55    ///
56    /// When [`Bitset::bit(bit)`] will be called next, it will be `true`
57    /// if this returned `Some`.
58    #[inline]
59    pub fn enable_bit(&mut self, width: usize, x: usize, y: usize) -> Option<()> {
60        if width == 0 {
61            return Some(());
62        }
63        self.0.enable_bit(width * y + x)
64    }
65    /// Create a [`BitMatrix`] with given proportions.
66    ///
67    /// Note that the total size is the lowest multiple of 32 higher or equal to `width * height`.
68    #[must_use]
69    pub fn new_with_size(width: usize, height: usize) -> Self {
70        let bit_size = width * height;
71        let u32_size = div_ceil(bit_size, mem::size_of::<u32>());
72        BitMatrix(Bitset(vec![0; u32_size].into_boxed_slice()))
73    }
74
75    /// `true` if bit at position `x, y` in matrix is enabled.
76    ///
77    /// `false` otherwise, included if `x, y` is outside of the matrix.
78    #[must_use]
79    pub fn bit(&self, width: usize, x: usize, y: usize) -> bool {
80        x < width && self.0.bit(x + y * width)
81    }
82
83    /// Return a struct that, when printed with [`fmt::Display`] or [`fmt::Debug`],
84    /// displays the matrix using unicode sextant characters([pdf]).
85    ///
86    /// [pdf]: https://unicode.org/charts/PDF/U1FB00.pdf
87    #[must_use]
88    pub const fn sextant_display(&self, width: usize, height: usize) -> SextantDisplay {
89        SextantDisplay { matrix: self, width, height }
90    }
91}
92
93/// Iterator over a single column of a [`BitMatrix`],
94/// see [`BitMatrix::active_rows_in_column`] documentation for details.
95pub struct Column<'a> {
96    width: usize,
97    current_cell: usize,
98    data: &'a [u32],
99}
100impl Iterator for Column<'_> {
101    type Item = usize;
102
103    fn next(&mut self) -> Option<Self::Item> {
104        loop {
105            let bit = self.current_cell;
106            let row = self.current_cell / self.width;
107            self.current_cell += self.width;
108
109            let block = bit / u32::BITS as usize;
110            let offset = bit % u32::BITS as usize;
111
112            let is_active = |block: u32| block & (1 << offset) != 0;
113            match self.data.get(block) {
114                Some(block) if is_active(*block) => return Some(row),
115                Some(_) => continue,
116                None => return None,
117            }
118        }
119    }
120    #[inline]
121    fn size_hint(&self) -> (usize, Option<usize>) {
122        let upper = self.data.len().saturating_sub(self.current_cell) / self.width;
123        (0, Some(upper))
124    }
125    #[inline]
126    fn nth(&mut self, n: usize) -> Option<Self::Item> {
127        self.current_cell = self.current_cell.saturating_add(n * self.width);
128        self.next()
129    }
130}
131
132/// Nice printing for [`BitMatrix`], see [`BitMatrix::sextant_display`] for details.
133#[derive(Copy, Clone)]
134pub struct SextantDisplay<'a> {
135    matrix: &'a BitMatrix,
136    width: usize,
137    height: usize,
138}
139impl<'a> fmt::Debug for SextantDisplay<'a> {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        fmt::Display::fmt(self, f)
142    }
143}
144impl<'a> fmt::Display for SextantDisplay<'a> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        if self.height == 0 {
147            write!(f, "\u{1fb74}\u{1fb70}")?;
148        }
149        for y in 0..div_ceil(self.height, 3) {
150            if y != 0 {
151                writeln!(f)?;
152            }
153            write!(f, "\u{1fb74}")?;
154            for x in 0..div_ceil(self.width, 2) {
155                let get_bit = |offset_x, offset_y| {
156                    let (x, y) = (x * 2 + offset_x, y * 3 + offset_y);
157                    u32::from(self.matrix.bit(self.width, x, y))
158                };
159                let offset = get_bit(0, 0)
160                    | get_bit(1, 0) << 1
161                    | get_bit(0, 1) << 2
162                    | get_bit(1, 1) << 3
163                    | get_bit(0, 2) << 4
164                    | get_bit(1, 2) << 5;
165                let character = match offset {
166                    0b11_1111 => '\u{2588}',
167                    0b00_0000 => ' ',
168                    offset => char::from_u32(0x1fb00 + offset - 1).unwrap(),
169                };
170                write!(f, "{character}")?;
171            }
172            write!(f, "\u{1fb70}")?;
173        }
174        Ok(())
175    }
176}