use bit_set::BitSet;
use crate::FormalContext;
fn next_concept<T>(context: &FormalContext<T>, a: &BitSet) -> Option<(BitSet, BitSet)> {
let mut a_new = a.clone();
for i in (0..context.attributes.len()).rev() {
if a.contains(i) {
a_new.remove(i);
} else {
let mut b = a_new.clone();
b.insert(i);
let gs = context.index_extent(&b);
let b_closure = context.index_intent(&gs);
if let Some(min_diff) = b_closure.difference(&a_new).next() {
if min_diff >= i {
return Some((gs, b_closure));
}
} else {
}
}
}
None
}
pub fn index_next_closure_concepts<'a, T>(
context: &'a FormalContext<T>,
) -> impl Iterator<Item = (BitSet, BitSet)> + 'a {
let gs = context.index_extent(&BitSet::new());
let ms = context.index_intent(&gs);
let mut next = Some((gs, ms));
std::iter::from_fn(move || {
if let Some((g, m)) = next.clone() {
next = next_concept(context, &m);
Some((g, m))
} else {
None
}
})
}
pub struct NextClosure;
impl<T> crate::traits::ConceptEnumerator<T> for NextClosure {
fn enumerate_concepts<'ctx>(
&self,
context: &'ctx crate::FormalContext<T>,
) -> impl Iterator<Item = (BitSet, BitSet)> + 'ctx {
index_next_closure_concepts(context)
}
}
#[cfg(test)]
mod tests {
use crate::{algorithms::next_closure::index_next_closure_concepts, FormalContext};
use bit_set::BitSet;
use itertools::Itertools;
use std::{collections::BTreeSet, fs};
#[test]
fn test_concepts() {
let context = FormalContext::<String>::from(
&fs::read("test_data/living_beings_and_water.cxt").unwrap(),
)
.unwrap();
let concepts: BTreeSet<_> = index_next_closure_concepts(&context).map(|(_, x)| x).collect();
let mut concepts_val = BTreeSet::new();
for ms in (0..context.attributes.len()).powerset() {
let sub: BitSet = ms.into_iter().collect();
let hull = context.index_attribute_hull(&sub);
if sub == hull {
concepts_val.insert(hull);
}
}
assert_eq!(concepts, concepts_val);
}
fn brute_force_concept_intents(ctx: &FormalContext<usize>) -> BTreeSet<BitSet> {
let mut result = BTreeSet::new();
for ms in (0..ctx.attributes.len()).powerset() {
let sub: BitSet = ms.into_iter().collect();
let hull = ctx.index_attribute_hull(&sub);
if sub == hull {
result.insert(hull);
}
}
result
}
fn build_context(n_attrs: usize, incidences: &[(usize, usize)]) -> FormalContext<usize> {
let n_objs = incidences.iter().map(|&(g, _)| g).max().map_or(0, |m| m + 1);
let mut ctx = FormalContext::<usize>::new();
for m in 0..n_attrs {
ctx.add_attribute(m, &BitSet::new());
}
for g in 0..n_objs {
let intent: BitSet = incidences.iter().filter(|&&(og, _)| og == g).map(|&(_, m)| m).collect();
ctx.add_object(g, &intent);
}
ctx
}
#[test]
fn test_concepts_zero_objects() {
let mut ctx = FormalContext::<usize>::new();
for m in 0..5 {
ctx.add_attribute(m, &BitSet::new());
}
let concepts: Vec<_> = index_next_closure_concepts(&ctx).collect();
assert_eq!(concepts.len(), 1);
assert!(concepts[0].0.is_empty(), "extent must be empty");
assert_eq!(concepts[0].1, (0..5).collect::<BitSet>(), "intent must be M");
}
#[test]
fn test_concepts_zero_attributes() {
let mut ctx = FormalContext::<usize>::new();
for g in 0..5 {
ctx.add_object(g, &BitSet::new());
}
let concepts: Vec<_> = index_next_closure_concepts(&ctx).collect();
assert_eq!(concepts.len(), 1);
assert_eq!(concepts[0].0, (0..5).collect::<BitSet>(), "extent must be G");
assert!(concepts[0].1.is_empty(), "intent must be empty");
}
#[test]
fn test_concepts_single_object_full() {
let mut ctx = FormalContext::<usize>::new();
for m in 0..5 {
ctx.add_attribute(m, &BitSet::new());
}
let full_intent: BitSet = (0..5).collect();
ctx.add_object(0, &full_intent);
let intents: BTreeSet<_> = index_next_closure_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(intents, brute_force_concept_intents(&ctx));
}
#[test]
fn test_concepts_full_context() {
let incidences: Vec<(usize, usize)> = (0..5).flat_map(|g| (0..5).map(move |m| (g, m))).collect();
let ctx = build_context(5, &incidences);
let intents: BTreeSet<_> = index_next_closure_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(intents, brute_force_concept_intents(&ctx));
}
#[test]
fn test_concepts_random_20x20() {
use rand::{Rng, SeedableRng};
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let n = 20;
let mut ctx = FormalContext::<usize>::new();
for m in 0..n {
ctx.add_attribute(m, &BitSet::new());
}
for g in 0..n {
let intent: BitSet = (0..n).filter(|_| rng.gen::<bool>()).collect();
ctx.add_object(g, &intent);
}
let intents: BTreeSet<_> = index_next_closure_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(intents, brute_force_concept_intents(&ctx));
}
}