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}