Skip to main content

indexical/
matrix.rs

1//! An unordered collections of pairs `(R, C)`, implemented with a sparse bit-matrix.
2
3#![allow(clippy::needless_pass_by_value)]
4
5use rustc_hash::FxHashMap;
6use std::{fmt, hash::Hash};
7
8use crate::{
9    IndexedDomain, IndexedValue, ToIndex, bitset::BitSet, pointer::PointerFamily, set::IndexSet,
10};
11
12/// An unordered collections of pairs `(R, C)`, implemented with a sparse bit-matrix.
13///
14/// "Sparse" means "hash map from rows to bit-sets of columns". Subsequently, only column types `C` must be indexed,
15/// while row types `R` only need be hashable.
16pub struct IndexMatrix<'a, R, C: IndexedValue + 'a, S: BitSet, P: PointerFamily<'a>> {
17    pub(crate) matrix: FxHashMap<R, IndexSet<'a, C, S, P>>,
18    empty_set: IndexSet<'a, C, S, P>,
19    col_domain: P::Pointer<IndexedDomain<C>>,
20}
21
22impl<'a, R, C, S, P> IndexMatrix<'a, R, C, S, P>
23where
24    R: PartialEq + Eq + Hash + Clone,
25    C: IndexedValue + 'a,
26    S: BitSet,
27    P: PointerFamily<'a>,
28{
29    /// Creates an empty matrix.
30    pub fn new(col_domain: &P::Pointer<IndexedDomain<C>>) -> Self {
31        IndexMatrix {
32            matrix: FxHashMap::default(),
33            empty_set: IndexSet::new(col_domain),
34            col_domain: col_domain.clone(),
35        }
36    }
37
38    pub(crate) fn ensure_row(&mut self, row: R) -> &mut IndexSet<'a, C, S, P> {
39        self.matrix
40            .entry(row)
41            .or_insert_with(|| self.empty_set.clone())
42    }
43
44    /// Inserts a pair `(row, col)` into the matrix, returning true if `self` changed.
45    pub fn insert<M>(&mut self, row: R, col: impl ToIndex<C, M>) -> bool {
46        let col = col.to_index(&self.col_domain);
47        self.ensure_row(row).insert(col)
48    }
49
50    /// Adds all elements of `from` into the row `into`.
51    pub fn union_into_row(&mut self, into: R, from: &IndexSet<'a, C, S, P>) -> bool {
52        self.ensure_row(into).union_changed(from)
53    }
54
55    /// Adds all elements from the row `from` into the row `into`.
56    pub fn union_rows(&mut self, from: R, to: R) -> bool {
57        if from == to {
58            return false;
59        }
60
61        self.ensure_row(from.clone());
62        self.ensure_row(to.clone());
63
64        // SAFETY: `from` != `to` therefore this is a disjoint mutable borrow
65        let [Some(from), Some(to)] =
66            (unsafe { self.matrix.get_disjoint_unchecked_mut([&from, &to]) })
67        else {
68            unreachable!()
69        };
70        to.union_changed(from)
71    }
72
73    /// Returns an iterator over the elements in `row`.
74    pub fn row(&self, row: &R) -> impl Iterator<Item = &C> {
75        self.matrix.get(row).into_iter().flat_map(IndexSet::iter)
76    }
77
78    /// Returns an iterator over all rows in the matrix.
79    pub fn rows(&self) -> impl ExactSizeIterator<Item = (&R, &IndexSet<'a, C, S, P>)> {
80        self.matrix.iter()
81    }
82
83    /// Returns the [`IndexSet`] for a particular `row`.
84    pub fn row_set(&self, row: &R) -> &IndexSet<'a, C, S, P> {
85        self.matrix.get(row).unwrap_or(&self.empty_set)
86    }
87
88    /// Clears all the elements from the `row`.
89    pub fn clear_row(&mut self, row: &R) {
90        self.matrix.remove(row);
91    }
92
93    /// Returns the [`IndexedDomain`] for the column type.
94    pub fn col_domain(&self) -> &P::Pointer<IndexedDomain<C>> {
95        &self.col_domain
96    }
97}
98
99impl<'a, R, C, S, P> IndexMatrix<'a, R, C, S, P>
100where
101    R: PartialEq + Eq + Hash + Clone + 'a,
102    C: IndexedValue + 'a,
103    S: BitSet,
104    P: PointerFamily<'a>,
105{
106    // R = Local
107    // C = Allocation
108
109    /// Transposes the matrix, assuming the row type is also indexed.
110    pub fn transpose<T, M>(
111        &self,
112        row_domain: &P::Pointer<IndexedDomain<T>>,
113    ) -> IndexMatrix<'a, C::Index, T, S, P>
114    where
115        T: IndexedValue + 'a,
116        R: ToIndex<T, M>,
117    {
118        let mut mtx = IndexMatrix::new(row_domain);
119        for (row, cols) in self.rows() {
120            for col in cols.indices() {
121                mtx.insert(col, row.clone());
122            }
123        }
124        mtx
125    }
126}
127
128impl<'a, R, C, S, P> PartialEq for IndexMatrix<'a, R, C, S, P>
129where
130    R: PartialEq + Eq + Hash + Clone,
131    C: IndexedValue + 'a,
132    S: BitSet,
133    P: PointerFamily<'a>,
134{
135    fn eq(&self, other: &Self) -> bool {
136        self.matrix == other.matrix
137    }
138}
139
140impl<'a, R, C, S, P> Eq for IndexMatrix<'a, R, C, S, P>
141where
142    R: PartialEq + Eq + Hash + Clone,
143    C: IndexedValue + 'a,
144    S: BitSet,
145    P: PointerFamily<'a>,
146{
147}
148
149impl<'a, R, C, S, P> Clone for IndexMatrix<'a, R, C, S, P>
150where
151    R: PartialEq + Eq + Hash + Clone,
152    C: IndexedValue + 'a,
153    S: BitSet,
154    P: PointerFamily<'a>,
155{
156    fn clone(&self) -> Self {
157        Self {
158            matrix: self.matrix.clone(),
159            empty_set: self.empty_set.clone(),
160            col_domain: self.col_domain.clone(),
161        }
162    }
163
164    fn clone_from(&mut self, source: &Self) {
165        for col in self.matrix.values_mut() {
166            col.clear();
167        }
168
169        for (row, col) in &source.matrix {
170            self.ensure_row(row.clone()).clone_from(col);
171        }
172
173        self.empty_set.clone_from(&source.empty_set);
174        self.col_domain.clone_from(&source.col_domain);
175    }
176}
177
178impl<'a, R, C, S, P> fmt::Debug for IndexMatrix<'a, R, C, S, P>
179where
180    R: PartialEq + Eq + Hash + Clone + fmt::Debug,
181    C: IndexedValue + fmt::Debug + 'a,
182    S: BitSet,
183    P: PointerFamily<'a>,
184{
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        f.debug_map().entries(self.rows()).finish()
187    }
188}
189
190#[cfg(test)]
191mod test {
192    use crate::{IndexedDomain, test_utils::TestIndexMatrix};
193    use std::rc::Rc;
194
195    fn mk(s: &str) -> String {
196        s.to_string()
197    }
198
199    #[test]
200    fn test_indexmatrix() {
201        let col_domain = Rc::new(IndexedDomain::from_iter([mk("a"), mk("b"), mk("c")]));
202        let mut mtx = TestIndexMatrix::new(&col_domain);
203        mtx.insert(0, mk("b"));
204        mtx.insert(1, mk("c"));
205        assert_eq!(mtx.row(&0).collect::<Vec<_>>(), vec!["b"]);
206        assert_eq!(mtx.row(&1).collect::<Vec<_>>(), vec!["c"]);
207
208        assert!(mtx.union_rows(0, 1));
209        assert_eq!(mtx.row(&1).collect::<Vec<_>>(), vec!["b", "c"]);
210    }
211}