use std::collections::HashSet;
use crate::data_structures::{formal_context::FormalContext, iceberg_lattice::IcebergLattice};
pub trait IcebergConceptEnumerator {
fn enumerate<T>(&self, ctx: &FormalContext<T>, min_support: u32) -> IcebergLattice;
fn enumerate_named_concepts<T>(
&self,
ctx: &FormalContext<T>,
min_support: u32,
) -> Vec<(HashSet<T>, HashSet<T>)>
where
T: Eq + std::hash::Hash + Clone,
{
self.enumerate(ctx, min_support)
.poset
.nodes
.iter()
.map(|(extent_bits, intent_bits)| {
let extent = extent_bits.iter().map(|i| ctx.objects[i].clone()).collect();
let intent = intent_bits.iter().map(|i| ctx.attributes[i].clone()).collect();
(extent, intent)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use std::{fs};
use crate::{algorithms::Titanic, traits::IcebergConceptEnumerator, FormalContext};
fn load_living_beings() -> FormalContext<String> {
FormalContext::<String>::from(
&fs::read("test_data/living_beings_and_water.cxt").unwrap(),
)
.unwrap()
}
#[test]
fn test_enumerate_named_concepts_count_matches_enumerate() {
let ctx = load_living_beings();
let min_support = 3;
let lattice = Titanic.enumerate(&ctx, min_support);
let named = Titanic.enumerate_named_concepts(&ctx, min_support);
assert_eq!(
named.len(),
lattice.poset.nodes.len(),
"enumerate_named_concepts count must match enumerate node count"
);
}
#[test]
fn test_enumerate_named_concepts_min_support_exceeds_objects_is_empty() {
let ctx = load_living_beings();
let min_support = ctx.objects.len() as u32 + 1;
let named = Titanic.enumerate_named_concepts(&ctx, min_support);
assert!(
named.is_empty(),
"enumerate_named_concepts should return empty vec when min_support > object count"
);
}
#[test]
fn test_enumerate_named_concepts_empty_context_is_empty() {
let ctx = FormalContext::<String>::new();
let named = Titanic.enumerate_named_concepts(&ctx, 0);
assert!(
named.is_empty(),
"enumerate_named_concepts on empty context should return empty vec"
);
}
}