Skip to main content

quorum_set/quorum/
grid_quorum_set.rs

1use std::collections::BTreeSet;
2use std::collections::btree_set;
3
4use crate::quorum::GridQuorumSetError;
5use crate::quorum::QuorumIntersection;
6use crate::quorum::QuorumSet;
7
8/// How a [`GridQuorumSet`] counts the rows of its grid: a full row, or one ID of each row.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum GridRule {
11    /// An accepted set holds every ID of some row.
12    FullRow,
13
14    /// An accepted set holds at least one ID of every row. Each row can contribute an ID from
15    /// a different column.
16    OnePerRow,
17}
18
19/// A quorum rule that accepts an ID set holding every ID of some row of its grid, or at least
20/// one ID of every row.
21///
22/// The grid is a rectangle of distinct IDs, given as its rows. [`GridRule`] selects whether an
23/// accepted set holds a full row or one ID of each row.
24///
25/// The rows of a grid are disjoint, so no row holds another, and the minimal quorums of the
26/// `FullRow` rule are exactly the rows. The minimal quorums of the `OnePerRow` rule hold one ID
27/// of each row, from any column. Each row meets every `OnePerRow` quorum of its grid, so the
28/// `FullRow` rule and the `OnePerRow` rule of one grid always intersect.
29///
30/// # Examples
31///
32/// ```
33/// use quorum_set::{GridQuorumSet, GridRule, QuorumIntersection, QuorumSet};
34///
35/// let grid = vec![vec![1, 2, 3], vec![4, 5, 6]];
36/// let full_row = GridQuorumSet::new(grid.clone(), GridRule::FullRow).unwrap();
37/// let one_per_row = GridQuorumSet::new(grid, GridRule::OnePerRow).unwrap();
38///
39/// let accepts = full_row.is_quorum([4, 5, 6].iter());
40/// assert!(accepts);
41/// // {1,5} and {3,4} each hold an ID of both rows, from different columns.
42/// let accepts = one_per_row.is_quorum([1, 5].iter());
43/// assert!(accepts);
44/// let accepts = one_per_row.is_quorum([3, 4].iter());
45/// assert!(accepts);
46/// // {1,2,3} holds no ID of row {4,5,6}.
47/// let accepts = one_per_row.is_quorum([1, 2, 3].iter());
48/// assert!(!accepts);
49///
50/// // Every row meets every `OnePerRow` quorum, but row {1,2,3} misses row {4,5,6}.
51/// let intersects = full_row.intersects_with(&one_per_row);
52/// assert_eq!(Some(true), intersects);
53/// let intersects = full_row.intersects_with(&full_row);
54/// assert_eq!(Some(false), intersects);
55/// ```
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct GridQuorumSet<ID> {
58    /// The rows of the grid, each one the IDs of its cells.
59    grid: Vec<Vec<ID>>,
60
61    /// Whether an accepted set holds a full row or one ID of each row.
62    rule: GridRule,
63}
64
65impl<ID> GridQuorumSet<ID>
66where ID: Ord + Clone
67{
68    /// Builds a rule over `grid`, given as its rows, that accepts an ID set holding a full row or
69    /// one ID of each row, as `rule` selects.
70    ///
71    /// Returns [`GridQuorumSetError`] unless `grid` has a row and a column, every row is as long
72    /// as the first, and no ID is in two cells.
73    pub fn new(grid: Vec<Vec<ID>>, rule: GridRule) -> Result<Self, GridQuorumSetError<ID>> {
74        check_grid(&grid)?;
75        let quorum_set = Self { grid, rule };
76        Ok(quorum_set)
77    }
78
79    /// Returns the grid, as its rows.
80    pub fn grid(&self) -> &[Vec<ID>] {
81        &self.grid
82    }
83
84    /// Returns whether an accepted set holds a full row or one ID of each row.
85    pub fn rule(&self) -> GridRule {
86        self.rule
87    }
88
89    /// Returns whether `rule` over `grid` accepts `candidates`.
90    fn accepts(grid: &[Vec<ID>], rule: GridRule, candidates: &BTreeSet<&ID>) -> bool {
91        match rule {
92            GridRule::FullRow => {
93                for row in grid.iter() {
94                    let holds_row = row.iter().all(|id| candidates.contains(id));
95                    if holds_row {
96                        return true;
97                    }
98                }
99                false
100            }
101            GridRule::OnePerRow => {
102                for row in grid.iter() {
103                    let meets_row = row.iter().any(|id| candidates.contains(id));
104                    if !meets_row {
105                        return false;
106                    }
107                }
108                true
109            }
110        }
111    }
112}
113
114impl<ID> QuorumSet for GridQuorumSet<ID>
115where ID: Ord + Clone
116{
117    type Id = ID;
118    type Iter = btree_set::IntoIter<ID>;
119
120    fn is_quorum<'a, I>(&self, ids: I) -> bool
121    where
122        ID: 'a,
123        I: Iterator<Item = &'a ID> + Clone,
124    {
125        let candidates: BTreeSet<&ID> = ids.collect();
126        Self::accepts(&self.grid, self.rule, &candidates)
127    }
128
129    fn ids(&self) -> Self::Iter {
130        let cells = self.grid.iter().flatten();
131        let ids: BTreeSet<ID> = cells.cloned().collect();
132        ids.into_iter()
133    }
134}
135
136/// Quorum intersection for two grid rules, exact when either rule is [`GridRule::FullRow`].
137///
138/// A set meets every quorum of a rule exactly when the other [`GridRule`] over the same grid
139/// accepts the set:
140///
141/// - A set meets every row exactly when it holds an ID of every row.
142/// - A set meets every `OnePerRow` quorum exactly when it holds a full row. A set without a full
143///   row misses an ID of each row, and these IDs form a `OnePerRow` quorum that the set misses.
144///
145/// Every quorum of a `FullRow` rule holds one of its rows, and every row is a quorum. A `FullRow`
146/// rule therefore intersects another rule exactly when each of its rows meets every quorum of the
147/// other rule. The check gives no answer for two `OnePerRow` rules. A `FullRow` read rule with a
148/// `OnePerRow` write rule over one grid, or the reverse, always gets an exact answer.
149impl<ID> QuorumIntersection<GridQuorumSet<ID>> for GridQuorumSet<ID>
150where ID: Ord + Clone
151{
152    /// Return `Some(false)` when a row of a `FullRow` rule misses a quorum of the other rule, and
153    /// `Some(true)` otherwise. Return `None` for two `OnePerRow` rules.
154    fn intersects_with(&self, other: &GridQuorumSet<ID>) -> Option<bool> {
155        let (full_row, compared) = match (self.rule, other.rule) {
156            (GridRule::FullRow, _) => (self, other),
157            (_, GridRule::FullRow) => (other, self),
158            (GridRule::OnePerRow, GridRule::OnePerRow) => return None,
159        };
160
161        // A set meets every quorum of `compared` exactly when the other rule over its grid accepts
162        // the set.
163        let meeting_rule = match compared.rule {
164            GridRule::FullRow => GridRule::OnePerRow,
165            GridRule::OnePerRow => GridRule::FullRow,
166        };
167        for row in full_row.grid.iter() {
168            let candidates = row.iter().collect();
169            let meets_every_quorum = Self::accepts(&compared.grid, meeting_rule, &candidates);
170            if !meets_every_quorum {
171                return Some(false);
172            }
173        }
174        Some(true)
175    }
176}
177
178/// Returns an error unless `grid` has a row and a column, every row is as long as the first, and
179/// no ID is in two cells.
180fn check_grid<ID>(grid: &[Vec<ID>]) -> Result<(), GridQuorumSetError<ID>>
181where ID: Ord + Clone {
182    let Some(first_row) = grid.first() else {
183        return Err(GridQuorumSetError::NoRow);
184    };
185    let column_count = first_row.len();
186    if column_count == 0 {
187        return Err(GridQuorumSetError::NoColumn);
188    }
189
190    for (row, cells) in grid.iter().enumerate() {
191        let len = cells.len();
192        if len != column_count {
193            let ragged = GridQuorumSetError::RaggedRow {
194                row,
195                len,
196                column_count,
197            };
198            return Err(ragged);
199        }
200    }
201
202    let mut seen = BTreeSet::new();
203    for id in grid.iter().flatten() {
204        let is_new = seen.insert(id);
205        if !is_new {
206            let duplicate = GridQuorumSetError::DuplicateId { id: id.clone() };
207            return Err(duplicate);
208        }
209    }
210    Ok(())
211}