Skip to main content

problemreductions/models/set/
minimum_cardinality_key.rs

1//! Minimum Cardinality Key problem implementation.
2//!
3//! Given a set of attribute names and functional dependencies,
4//! find a candidate key of minimum cardinality.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12    ProblemSchemaEntry {
13        name: "MinimumCardinalityKey",
14        display_name: "Minimum Cardinality Key",
15        aliases: &[],
16        dimensions: &[],
17        category: crate::registry::ProblemCategory::Set,
18        module_path: module_path!(),
19        description: "Find a candidate key of minimum cardinality in a relational system",
20        fields: &[
21            FieldInfo { name: "num_attributes", type_name: "usize", description: "Number of attributes in the relation" },
22            FieldInfo { name: "dependencies", type_name: "Vec<(Vec<usize>, Vec<usize>)>", description: "Functional dependencies as (lhs, rhs) pairs" },
23        ],
24    }
25}
26
27/// The Minimum Cardinality Key optimization problem.
28///
29/// Given a set of attributes `A = {0, ..., n-1}` and a set of functional
30/// dependencies `F` (each a pair `(X, Y)` where `X, Y` are subsets of `A`),
31/// find a subset `K ⊆ A` of minimum cardinality such that the closure of `K`
32/// under `F` equals `A` (i.e., `K` is a key).
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct MinimumCardinalityKey {
35    /// Number of attributes (elements are `0..num_attributes`).
36    num_attributes: usize,
37    /// Functional dependencies as `(lhs, rhs)` pairs.
38    dependencies: Vec<(Vec<usize>, Vec<usize>)>,
39}
40
41impl MinimumCardinalityKey {
42    /// Create a new Minimum Cardinality Key instance.
43    ///
44    /// # Panics
45    ///
46    /// Panics if any attribute index in a dependency lies outside the attribute set.
47    pub fn new(num_attributes: usize, dependencies: Vec<(Vec<usize>, Vec<usize>)>) -> Self {
48        let mut dependencies = dependencies;
49        for (dep_index, (lhs, rhs)) in dependencies.iter_mut().enumerate() {
50            lhs.sort_unstable();
51            lhs.dedup();
52            rhs.sort_unstable();
53            rhs.dedup();
54            for &attr in lhs.iter().chain(rhs.iter()) {
55                assert!(
56                    attr < num_attributes,
57                    "Dependency {} contains attribute {} which is outside attribute set of size {}",
58                    dep_index,
59                    attr,
60                    num_attributes
61                );
62            }
63        }
64
65        Self {
66            num_attributes,
67            dependencies,
68        }
69    }
70
71    /// Return the number of attributes.
72    pub fn num_attributes(&self) -> usize {
73        self.num_attributes
74    }
75
76    /// Return the number of functional dependencies.
77    pub fn num_dependencies(&self) -> usize {
78        self.dependencies.len()
79    }
80
81    /// Return the functional dependencies.
82    pub fn dependencies(&self) -> &[(Vec<usize>, Vec<usize>)] {
83        &self.dependencies
84    }
85
86    /// Compute the attribute closure of the selected attributes under the
87    /// functional dependencies. Starts with the selected set and repeatedly
88    /// applies each FD: if all lhs attributes are in the closure, add all rhs
89    /// attributes. Repeats until no change.
90    fn compute_closure(&self, selected: &[bool]) -> Vec<bool> {
91        let mut closure = selected.to_vec();
92        loop {
93            let mut changed = false;
94            for (lhs, rhs) in &self.dependencies {
95                if lhs.iter().all(|&a| closure[a]) {
96                    for &a in rhs {
97                        if !closure[a] {
98                            closure[a] = true;
99                            changed = true;
100                        }
101                    }
102                }
103            }
104            if !changed {
105                break;
106            }
107        }
108        closure
109    }
110
111    /// Check whether the selected attributes form a key (their closure equals
112    /// the full attribute set).
113    fn is_key(&self, selected: &[bool]) -> bool {
114        let closure = self.compute_closure(selected);
115        closure.iter().all(|&v| v)
116    }
117}
118
119impl Problem for MinimumCardinalityKey {
120    const NAME: &'static str = "MinimumCardinalityKey";
121    type Solution = Vec<bool>;
122    type Value = Min<i64>;
123
124    crate::problem_parameters![
125        ("num_attributes", num_attributes),
126        ("num_dependencies", num_dependencies),
127    ];
128
129    fn evaluate(
130        &self,
131        config: &Self::Solution,
132    ) -> Result<Min<i64>, crate::traits::EvaluationError> {
133        Ok({
134            if config.len() != self.num_attributes {
135                return Err(crate::traits::EvaluationError::InvalidConfiguration(
136                    "attribute-selection length does not match the relation".into(),
137                ));
138            }
139
140            if self.is_key(config) {
141                let count = config.iter().filter(|&&v| v).count();
142                Min(Some(i64::try_from(count).map_err(|_| {
143                    crate::traits::EvaluationError::IntegerOverflow(
144                        "converting selected-attribute count to i64".into(),
145                    )
146                })?))
147            } else {
148                Min(None)
149            }
150        })
151    }
152
153    fn variant() -> Vec<(&'static str, &'static str)> {
154        crate::variant_params![]
155    }
156}
157
158impl crate::solvers::BruteForceProblem for MinimumCardinalityKey {
159    fn dimensions(&self) -> Vec<usize> {
160        vec![2; self.num_attributes]
161    }
162}
163
164crate::declare_variants! {
165    default MinimumCardinalityKey => "2^num_attributes",
166}
167
168crate::register_brute_force! {
169    MinimumCardinalityKey decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
170}
171
172#[cfg(feature = "example-db")]
173pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
174    vec![crate::example_db::specs::ModelExampleSpec {
175        id: "minimum_cardinality_key",
176        instance: Box::new(MinimumCardinalityKey::new(
177            6,
178            vec![
179                (vec![0, 1], vec![2]),
180                (vec![0, 2], vec![3]),
181                (vec![1, 3], vec![4]),
182                (vec![2, 4], vec![5]),
183            ],
184        )),
185        optimal_config: serde_json::json!(vec![true, true, false, false, false, false]),
186        optimal_value: serde_json::json!(2),
187    }]
188}
189
190#[cfg(test)]
191#[path = "../../unit_tests/models/set/minimum_cardinality_key.rs"]
192mod tests;