Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions problemreductions-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7428,7 +7428,7 @@ fn test_create_bcnf_rejects_out_of_range_attribute_indices() {
"CLI should return a user-facing error, got: {stderr}"
);
assert!(
stderr.contains("outside universe of size 3"),
stderr.contains("out of range (num_attributes = 3)"),
"expected out-of-range error, got: {stderr}"
);
}
Expand All @@ -7454,7 +7454,7 @@ fn test_create_bcnf_rejects_out_of_range_lhs_attribute_indices() {
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("subsets[0] contains attribute 4 outside universe of size 3"),
stderr.contains("Functional dependency 0 contains attribute 4 which is out of range"),
"expected lhs-specific out-of-range error, got: {stderr}"
);
}
Expand All @@ -7480,7 +7480,7 @@ fn test_create_bcnf_rejects_out_of_range_target_attribute_indices() {
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("target contains attribute 4 outside universe of size 3"),
stderr.contains("target_subset contains attribute 4 which is out of range"),
"expected target-specific out-of-range error, got: {stderr}"
);
}
Expand Down
83 changes: 60 additions & 23 deletions src/models/misc/additional_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,34 @@ inventory::submit! {
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "AdditionalKeyData")]
pub struct AdditionalKey {
num_attributes: usize,
dependencies: Vec<(Vec<usize>, Vec<usize>)>,
relation_attrs: Vec<usize>,
known_keys: Vec<Vec<usize>>,
}

#[derive(Deserialize)]
struct AdditionalKeyData {
num_attributes: usize,
dependencies: Vec<(Vec<usize>, Vec<usize>)>,
relation_attrs: Vec<usize>,
known_keys: Vec<Vec<usize>>,
}

impl TryFrom<AdditionalKeyData> for AdditionalKey {
type Error = crate::registry::ConstructionError;
fn try_from(data: AdditionalKeyData) -> Result<Self, Self::Error> {
Self::try_new(
data.num_attributes,
data.dependencies,
data.relation_attrs,
data.known_keys,
)
}
}

impl AdditionalKey {
/// Create a new AdditionalKey instance.
///
Expand All @@ -81,42 +102,58 @@ impl AdditionalKey {
relation_attrs: Vec<usize>,
known_keys: Vec<Vec<usize>>,
) -> Self {
Self::try_new(num_attributes, dependencies, relation_attrs, known_keys)
.unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
num_attributes: usize,
dependencies: Vec<(Vec<usize>, Vec<usize>)>,
relation_attrs: Vec<usize>,
known_keys: Vec<Vec<usize>>,
) -> Result<Self, crate::registry::ConstructionError> {
// Validate all attribute indices
for &a in &relation_attrs {
assert!(
a < num_attributes,
"relation_attrs element {a} >= num_attributes {num_attributes}"
);
if a >= num_attributes {
return Err(format!(
"relation_attrs element {a} >= num_attributes {num_attributes}"
)
.into());
}
}
// Validate relation_attrs uniqueness
let mut sorted_ra = relation_attrs.clone();
sorted_ra.sort_unstable();
sorted_ra.dedup();
assert_eq!(
sorted_ra.len(),
relation_attrs.len(),
"relation_attrs contains duplicates"
);
if sorted_ra.len() != relation_attrs.len() {
return Err("relation_attrs contains duplicates".into());
}
for (lhs, rhs) in &dependencies {
for &a in lhs {
assert!(
a < num_attributes,
"dependency lhs attribute {a} >= num_attributes {num_attributes}"
);
if a >= num_attributes {
return Err(format!(
"dependency lhs attribute {a} >= num_attributes {num_attributes}"
)
.into());
}
}
for &a in rhs {
assert!(
a < num_attributes,
"dependency rhs attribute {a} >= num_attributes {num_attributes}"
);
if a >= num_attributes {
return Err(format!(
"dependency rhs attribute {a} >= num_attributes {num_attributes}"
)
.into());
}
}
}
for key in &known_keys {
for &a in key {
assert!(
a < num_attributes,
"known_keys attribute {a} >= num_attributes {num_attributes}"
);
if a >= num_attributes {
return Err(format!(
"known_keys attribute {a} >= num_attributes {num_attributes}"
)
.into());
}
}
}
// Sort known_keys entries internally for consistent comparison
Expand All @@ -127,12 +164,12 @@ impl AdditionalKey {
k
})
.collect();
Self {
Ok(Self {
num_attributes,
dependencies,
relation_attrs,
known_keys,
}
})
}

/// Returns the number of attributes in the universal set A.
Expand Down
90 changes: 43 additions & 47 deletions src/models/misc/boyce_codd_normal_form_violation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ inventory::submit! {
/// .unwrap());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "BoyceCoddNormalFormViolationData")]
pub struct BoyceCoddNormalFormViolation {
/// Total number of attributes (elements are `0..num_attributes`).
num_attributes: usize,
Expand All @@ -67,6 +68,24 @@ pub struct BoyceCoddNormalFormViolation {
target_subset: Vec<usize>,
}

#[derive(Deserialize)]
struct BoyceCoddNormalFormViolationData {
num_attributes: usize,
functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
target_subset: Vec<usize>,
}

impl TryFrom<BoyceCoddNormalFormViolationData> for BoyceCoddNormalFormViolation {
type Error = crate::registry::ConstructionError;
fn try_from(data: BoyceCoddNormalFormViolationData) -> Result<Self, Self::Error> {
Self::try_new(
data.num_attributes,
data.functional_deps,
data.target_subset,
)
}
}

#[derive(Debug, Deserialize, crate::CreateSpec)]
struct BoyceCoddNormalFormViolationCreateSpec {
/// Total number of attributes in A.
Expand All @@ -82,32 +101,7 @@ impl TryFrom<BoyceCoddNormalFormViolationCreateSpec> for BoyceCoddNormalFormViol
type Error = crate::registry::ConstructionError;

fn try_from(spec: BoyceCoddNormalFormViolationCreateSpec) -> Result<Self, Self::Error> {
if spec.target.is_empty() {
return Err("target must be non-empty".to_string().into());
}
for (dependency_index, (lhs, rhs)) in spec.subsets.iter().enumerate() {
if lhs.is_empty() {
return Err(format!("subsets[{dependency_index}] has an empty left side").into());
}
if let Some(&attribute) = lhs
.iter()
.chain(rhs)
.find(|&&attribute| attribute >= spec.n)
{
return Err(format!(
"subsets[{dependency_index}] contains attribute {attribute} outside universe of size {}",
spec.n
).into());
}
}
if let Some(&attribute) = spec.target.iter().find(|&&attribute| attribute >= spec.n) {
return Err(format!(
"target contains attribute {attribute} outside universe of size {}",
spec.n
)
.into());
}
Ok(Self::new(spec.n, spec.subsets, spec.target))
Self::try_new(spec.n, spec.subsets, spec.target)
}
}

Expand All @@ -129,47 +123,49 @@ impl BoyceCoddNormalFormViolation {
functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
target_subset: Vec<usize>,
) -> Self {
assert!(!target_subset.is_empty(), "target_subset must be non-empty");
Self::try_new(num_attributes, functional_deps, target_subset)
.unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
num_attributes: usize,
functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
target_subset: Vec<usize>,
) -> Result<Self, crate::registry::ConstructionError> {
if target_subset.is_empty() {
return Err("target_subset must be non-empty".into());
}

let mut functional_deps = functional_deps;
for (fd_index, (lhs, rhs)) in functional_deps.iter_mut().enumerate() {
assert!(
!lhs.is_empty(),
"Functional dependency {} has an empty LHS",
fd_index
);
if lhs.is_empty() {
return Err(format!("Functional dependency {} has an empty LHS", fd_index).into());
}
lhs.sort_unstable();
lhs.dedup();
rhs.sort_unstable();
rhs.dedup();
for &attr in lhs.iter().chain(rhs.iter()) {
assert!(
attr < num_attributes,
"Functional dependency {} contains attribute {} which is out of range (num_attributes = {})",
fd_index,
attr,
num_attributes
);
if attr >= num_attributes {
return Err(format!("Functional dependency {} contains attribute {} which is out of range (num_attributes = {})", fd_index, attr, num_attributes).into());
}
}
}

let mut target_subset = target_subset;
target_subset.sort_unstable();
target_subset.dedup();
for &attr in &target_subset {
assert!(
attr < num_attributes,
"target_subset contains attribute {} which is out of range (num_attributes = {})",
attr,
num_attributes
);
if attr >= num_attributes {
return Err(format!("target_subset contains attribute {} which is out of range (num_attributes = {})", attr, num_attributes).into());
}
}

Self {
Ok(Self {
num_attributes,
functional_deps,
target_subset,
}
})
}

/// Return the total number of attributes.
Expand Down
Loading
Loading