problemreductions/models/misc/
boyce_codd_normal_form_violation.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "BoyceCoddNormalFormViolation",
16 display_name: "Boyce-Codd Normal Form Violation",
17 aliases: &["BCNFViolation", "BCNF"],
18 dimensions: &[],
19 module_path: module_path!(),
20 description: "Test whether a subset of attributes violates Boyce-Codd normal form",
21 fields: &[
22 FieldInfo { name: "num_attributes", type_name: "usize", description: "Total number of attributes in A" },
23 FieldInfo { name: "functional_deps", type_name: "Vec<(Vec<usize>, Vec<usize>)>", description: "Functional dependencies (lhs_attributes, rhs_attributes)" },
24 FieldInfo { name: "target_subset", type_name: "Vec<usize>", description: "Subset A' of attributes to test for BCNF violation" },
25 ],
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct BoyceCoddNormalFormViolation {
63 num_attributes: usize,
65 functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
67 target_subset: Vec<usize>,
69}
70
71impl BoyceCoddNormalFormViolation {
72 pub fn new(
85 num_attributes: usize,
86 functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
87 target_subset: Vec<usize>,
88 ) -> Self {
89 assert!(!target_subset.is_empty(), "target_subset must be non-empty");
90
91 let mut functional_deps = functional_deps;
92 for (fd_index, (lhs, rhs)) in functional_deps.iter_mut().enumerate() {
93 assert!(
94 !lhs.is_empty(),
95 "Functional dependency {} has an empty LHS",
96 fd_index
97 );
98 lhs.sort_unstable();
99 lhs.dedup();
100 rhs.sort_unstable();
101 rhs.dedup();
102 for &attr in lhs.iter().chain(rhs.iter()) {
103 assert!(
104 attr < num_attributes,
105 "Functional dependency {} contains attribute {} which is out of range (num_attributes = {})",
106 fd_index,
107 attr,
108 num_attributes
109 );
110 }
111 }
112
113 let mut target_subset = target_subset;
114 target_subset.sort_unstable();
115 target_subset.dedup();
116 for &attr in &target_subset {
117 assert!(
118 attr < num_attributes,
119 "target_subset contains attribute {} which is out of range (num_attributes = {})",
120 attr,
121 num_attributes
122 );
123 }
124
125 Self {
126 num_attributes,
127 functional_deps,
128 target_subset,
129 }
130 }
131
132 pub fn num_attributes(&self) -> usize {
134 self.num_attributes
135 }
136
137 pub fn num_functional_deps(&self) -> usize {
139 self.functional_deps.len()
140 }
141
142 pub fn num_target_attributes(&self) -> usize {
144 self.target_subset.len()
145 }
146
147 pub fn functional_deps(&self) -> &[(Vec<usize>, Vec<usize>)] {
149 &self.functional_deps
150 }
151
152 pub fn target_subset(&self) -> &[usize] {
154 &self.target_subset
155 }
156
157 fn compute_closure(x: &HashSet<usize>, fds: &[(Vec<usize>, Vec<usize>)]) -> HashSet<usize> {
159 let mut closure = x.clone();
160 let mut changed = true;
161 while changed {
162 changed = false;
163 for (lhs, rhs) in fds {
164 if lhs.iter().all(|a| closure.contains(a)) {
165 for &a in rhs {
166 if closure.insert(a) {
167 changed = true;
168 }
169 }
170 }
171 }
172 }
173 closure
174 }
175}
176
177impl Problem for BoyceCoddNormalFormViolation {
178 const NAME: &'static str = "BoyceCoddNormalFormViolation";
179 type Value = crate::types::Or;
180
181 fn dims(&self) -> Vec<usize> {
182 vec![2; self.target_subset.len()]
183 }
184
185 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
186 crate::types::Or({
187 if config.len() != self.target_subset.len() || config.iter().any(|&v| v > 1) {
188 return crate::types::Or(false);
189 }
190 let x: HashSet<usize> = config
191 .iter()
192 .enumerate()
193 .filter(|(_, &v)| v == 1)
194 .map(|(i, _)| self.target_subset[i])
195 .collect();
196 let closure = Self::compute_closure(&x, &self.functional_deps);
197 let mut has_in_closure = false;
199 let mut has_not_in_closure = false;
200 for &a in &self.target_subset {
201 if !x.contains(&a) {
202 if closure.contains(&a) {
203 has_in_closure = true;
204 } else {
205 has_not_in_closure = true;
206 }
207 }
208 }
209 has_in_closure && has_not_in_closure
210 })
211 }
212
213 fn variant() -> Vec<(&'static str, &'static str)> {
214 crate::variant_params![]
215 }
216}
217
218crate::declare_variants! {
219 default BoyceCoddNormalFormViolation => "2^num_target_attributes * num_target_attributes^2 * num_functional_deps",
220}
221
222#[cfg(feature = "example-db")]
223pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
224 vec![crate::example_db::specs::ModelExampleSpec {
225 id: "boyce_codd_normal_form_violation",
226 instance: Box::new(BoyceCoddNormalFormViolation::new(
227 6,
228 vec![
229 (vec![0, 1], vec![2]),
230 (vec![2], vec![3]),
231 (vec![3, 4], vec![5]),
232 ],
233 vec![0, 1, 2, 3, 4, 5],
234 )),
235 optimal_config: vec![0, 0, 1, 0, 0, 0],
237 optimal_value: serde_json::json!(true),
238 }]
239}
240
241#[cfg(test)]
242#[path = "../../unit_tests/models/misc/boyce_codd_normal_form_violation.rs"]
243mod tests;