problemreductions/models/set/
exact_cover_by_3_sets.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10use std::collections::HashSet;
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "ExactCoverBy3Sets",
15 display_name: "Exact Cover by 3-Sets",
16 aliases: &["X3C"],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Set,
19 module_path: module_path!(),
20 description: "Determine if a collection of 3-element subsets contains an exact cover",
21 fields: ExactCoverBy3SetsCreateSpec::FIELDS,
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(try_from = "ExactCoverBy3SetsCreateSpec")]
56pub struct ExactCoverBy3Sets {
57 universe_size: usize,
59 subsets: Vec<[usize; 3]>,
61}
62
63#[derive(Debug, Deserialize, crate::CreateSpec)]
64struct ExactCoverBy3SetsCreateSpec {
65 universe_size: usize,
66 #[create(codec = "semicolon-separated")]
67 subsets: Vec<[usize; 3]>,
68}
69
70impl TryFrom<ExactCoverBy3SetsCreateSpec> for ExactCoverBy3Sets {
71 type Error = crate::registry::ConstructionError;
72
73 fn try_from(spec: ExactCoverBy3SetsCreateSpec) -> Result<Self, Self::Error> {
74 Self::try_new(spec.universe_size, spec.subsets)
75 }
76}
77
78impl ExactCoverBy3Sets {
79 pub fn new(universe_size: usize, subsets: Vec<[usize; 3]>) -> Self {
86 Self::try_new(universe_size, subsets).unwrap_or_else(|error| panic!("{error}"))
87 }
88
89 fn try_new(
90 universe_size: usize,
91 mut subsets: Vec<[usize; 3]>,
92 ) -> Result<Self, crate::registry::ConstructionError> {
93 if !universe_size.is_multiple_of(3) {
94 return Err(
95 format!("Universe size must be divisible by 3, got {universe_size}").into(),
96 );
97 }
98 for (index, subset) in subsets.iter_mut().enumerate() {
99 if subset[0] == subset[1] || subset[0] == subset[2] || subset[1] == subset[2] {
100 return Err(format!("subset {index} contains duplicate elements").into());
101 }
102 if let Some(&element) = subset.iter().find(|&&element| element >= universe_size) {
103 return Err(
104 format!("Subset {index} contains element {element} which is outside universe of size {universe_size}").into(),
105 );
106 }
107 subset.sort();
108 }
109 Ok(Self {
110 universe_size,
111 subsets,
112 })
113 }
114
115 pub fn universe_size(&self) -> usize {
117 self.universe_size
118 }
119
120 pub fn num_subsets(&self) -> usize {
122 self.subsets.len()
123 }
124
125 pub fn q(&self) -> usize {
130 self.universe_size / 3
131 }
132
133 pub fn num_sets(&self) -> usize {
135 self.num_subsets()
136 }
137
138 pub fn subsets(&self) -> &[[usize; 3]] {
140 &self.subsets
141 }
142
143 pub fn sets(&self) -> &[[usize; 3]] {
145 self.subsets()
146 }
147
148 pub fn get_subset(&self, index: usize) -> Option<&[usize; 3]> {
150 self.subsets.get(index)
151 }
152
153 pub fn is_valid_solution(
158 &self,
159 config: &[bool],
160 ) -> Result<bool, crate::traits::EvaluationError> {
161 if config.len() != self.subsets.len() {
162 return Err(crate::traits::EvaluationError::InvalidConfiguration(
163 "subset-selection length does not match the instance".into(),
164 ));
165 }
166
167 let q = self.universe_size / 3;
168 if config.iter().filter(|&&selected| selected).count() != q {
169 return Ok(false);
170 }
171
172 let mut covered = HashSet::with_capacity(self.universe_size);
173 for (subset, &selected) in self.subsets.iter().zip(config) {
174 if selected {
175 for &element in subset {
176 if !covered.insert(element) {
177 return Ok(false);
178 }
179 }
180 }
181 }
182 Ok(covered.len() == self.universe_size)
183 }
184
185 pub fn covered_elements(&self, config: &[bool]) -> HashSet<usize> {
187 let mut covered = HashSet::new();
188 for (i, &selected) in config.iter().enumerate() {
189 if selected {
190 if let Some(subset) = self.subsets.get(i) {
191 covered.extend(subset.iter().copied());
192 }
193 }
194 }
195 covered
196 }
197}
198
199impl Problem for ExactCoverBy3Sets {
200 const NAME: &'static str = "ExactCoverBy3Sets";
201 type Solution = Vec<bool>;
202 type Value = crate::types::Or;
203
204 crate::problem_parameters![
205 ("num_subsets", num_subsets),
206 ("universe_size", universe_size),
207 ];
208
209 fn evaluate(
210 &self,
211 config: &Self::Solution,
212 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
213 Ok(crate::types::Or(self.is_valid_solution(config)?))
214 }
215
216 fn variant() -> Vec<(&'static str, &'static str)> {
217 crate::variant_params![]
218 }
219}
220
221impl crate::solvers::BruteForceProblem for ExactCoverBy3Sets {
222 fn dimensions(&self) -> Vec<usize> {
223 vec![2; self.subsets.len()]
224 }
225}
226
227crate::declare_variants! {
228 default ExactCoverBy3Sets => "2^universe_size" create ExactCoverBy3SetsCreateSpec,
229}
230
231crate::register_brute_force! {
232 ExactCoverBy3Sets decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
233}
234
235#[cfg(feature = "example-db")]
236pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
237 vec![crate::example_db::specs::ModelExampleSpec {
238 id: "exact_cover_by_3_sets",
239 instance: Box::new(ExactCoverBy3Sets::new(
240 9,
241 vec![
242 [0, 1, 2],
243 [0, 2, 4],
244 [3, 4, 5],
245 [3, 5, 7],
246 [6, 7, 8],
247 [1, 4, 6],
248 [2, 5, 8],
249 ],
250 )),
251 optimal_config: serde_json::json!(vec![true, false, true, false, true, false, false]),
252 optimal_value: serde_json::json!(true),
253 }]
254}
255
256#[cfg(test)]
257#[path = "../../unit_tests/models/set/exact_cover_by_3_sets.rs"]
258mod tests;