use crate::Partitions;
use rbe::{Context, Key, MatchKind, RbeTable, Ref, Value};
use serde::Serialize;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
pub fn class_partitions_iter<'a, T, K, V, R, Ctx, P>(
neighs: &'a [(K, V, Ctx)],
exprs: &'a HashMap<T, Vec<RbeTable<K, V, R, Ctx, P>>>,
) -> ClassPartitionIterator<'a, T, K, V, R, Ctx, P>
where
K: Key,
V: Value,
R: Ref,
Ctx: Context,
P: MatchKind<K, V, R, Ctx> + Clone + PartialEq + Eq + Hash + Debug + Serialize,
T: std::hash::Hash + Eq + Clone,
{
ClassPartitionIterator::new(neighs, exprs)
}
struct Class<K, V, Ctx> {
values: Vec<(K, V, Ctx)>,
eligible: Vec<usize>,
}
pub struct ClassPartitionIterator<'a, T, K, V, R, Ctx, P>
where
K: Key,
V: Value,
R: Ref,
Ctx: Context,
P: MatchKind<K, V, R, Ctx> + Clone + PartialEq + Eq + Hash + Debug + Serialize,
T: std::hash::Hash + Eq + Clone,
{
buckets: Vec<(&'a T, &'a Vec<RbeTable<K, V, R, Ctx, P>>)>,
classes: Vec<Class<K, V, Ctx>>,
counts: Vec<Option<Vec<usize>>>,
level: usize,
perms: Option<Vec<Vec<usize>>>,
exhausted: bool,
}
impl<'a, T, K, V, R, Ctx, P> ClassPartitionIterator<'a, T, K, V, R, Ctx, P>
where
K: Key,
V: Value,
R: Ref,
Ctx: Context,
P: MatchKind<K, V, R, Ctx> + Clone + PartialEq + Eq + Hash + Debug + Serialize,
T: std::hash::Hash + Eq + Clone,
{
fn new(neighs: &'a [(K, V, Ctx)], exprs: &'a HashMap<T, Vec<RbeTable<K, V, R, Ctx, P>>>) -> Self {
let buckets: Vec<(&T, &Vec<RbeTable<K, V, R, Ctx, P>>)> = exprs.iter().collect();
let bucket_keys: Vec<Vec<&K>> = buckets
.iter()
.map(|(_, rbes)| rbes.iter().flat_map(|rbe| rbe.keys()).collect())
.collect();
let mut class_map: HashMap<Vec<usize>, Vec<(K, V, Ctx)>> = HashMap::new();
for (k, v, ctx) in neighs {
let eligible: Vec<usize> = (0..buckets.len()).filter(|b| bucket_keys[*b].contains(&k)).collect();
if eligible.is_empty() {
continue;
}
class_map
.entry(eligible)
.or_default()
.push((k.clone(), v.clone(), ctx.clone()));
}
let mut classes: Vec<Class<K, V, Ctx>> = class_map
.into_iter()
.map(|(eligible, values)| Class { values, eligible })
.collect();
classes.sort_by(|a, b| {
a.eligible
.len()
.cmp(&b.eligible.len())
.then(b.values.len().cmp(&a.values.len()))
});
let counts = vec![None; classes.len()];
let mut iter = ClassPartitionIterator {
buckets,
classes,
counts,
level: 0,
perms: None,
exhausted: false,
};
if !iter.feasible_now() {
iter.exhausted = true;
}
iter
}
fn bucket_pool(&self, b: usize) -> Vec<(K, V, Ctx)> {
let mut pool = Vec::new();
for (i, class) in self.classes.iter().enumerate() {
let Some(pos) = class.eligible.iter().position(|e| *e == b) else {
continue;
};
let reachable = match &self.counts[i] {
None => true,
Some(c) => c[pos] > 0,
};
if reachable {
pool.extend(class.values.iter().cloned());
}
}
pool
}
fn feasible_now(&self) -> bool {
for (b, (_, rbes)) in self.buckets.iter().enumerate() {
let pool = self.bucket_pool(b);
for rbe in rbes.iter() {
if !rbe.feasible_neighs(&pool) {
return false;
}
}
}
true
}
fn first_composition(&mut self, lvl: usize) {
let size = self.classes[lvl].values.len();
let e = self.classes[lvl].eligible.len();
let mut c = vec![0usize; e];
c[0] = size;
self.counts[lvl] = Some(c);
}
fn next_composition(&mut self, lvl: usize) -> bool {
let c = self.counts[lvl].as_mut().expect("composition to advance");
let e = c.len();
for i in (0..e.saturating_sub(1)).rev() {
if c[i] > 0 {
let right_sum: usize = c[i + 1..].iter().sum();
c[i] -= 1;
for x in c[i + 1..].iter_mut() {
*x = 0;
}
c[i + 1] = right_sum + 1;
return true;
}
}
self.counts[lvl] = None;
false
}
fn init_perms(&mut self) {
let mut perms = Vec::with_capacity(self.classes.len());
for (i, class) in self.classes.iter().enumerate() {
let c = self.counts[i].as_ref().expect("complete distribution");
let mut p = Vec::with_capacity(class.values.len());
for (pos, count) in c.iter().enumerate() {
for _ in 0..*count {
p.push(class.eligible[pos]);
}
}
p.sort_unstable(); perms.push(p);
}
self.perms = Some(perms);
}
fn advance_perms(&mut self) -> bool {
let perms = self.perms.as_mut().expect("expansion in progress");
for p in perms.iter_mut() {
if next_permutation(p) {
return true;
}
p.sort_unstable(); }
false
}
fn current_partition(&self) -> Partitions<T, K, V, R, Ctx, P> {
let perms = self.perms.as_ref().expect("expansion in progress");
let mut subsets: Vec<Vec<(K, V, Ctx)>> = vec![Vec::new(); self.buckets.len()];
for (i, class) in self.classes.iter().enumerate() {
for (vi, value) in class.values.iter().enumerate() {
subsets[perms[i][vi]].push(value.clone());
}
}
self.buckets
.iter()
.zip(subsets)
.map(|((t, rbes), subset)| ((*t).clone(), (*rbes).clone(), subset))
.collect()
}
}
impl<T, K, V, R, Ctx, P> Iterator for ClassPartitionIterator<'_, T, K, V, R, Ctx, P>
where
K: Key,
V: Value,
R: Ref,
Ctx: Context,
P: MatchKind<K, V, R, Ctx> + Clone + PartialEq + Eq + Hash + Debug + Serialize,
T: std::hash::Hash + Eq + Clone,
{
type Item = Partitions<T, K, V, R, Ctx, P>;
fn next(&mut self) -> Option<Self::Item> {
if self.exhausted {
return None;
}
if self.perms.is_some() {
if self.advance_perms() {
return Some(self.current_partition());
}
self.perms = None;
if self.level == 0 && self.classes.is_empty() {
self.exhausted = true;
return None;
}
self.level = self.level.saturating_sub(1);
if self.classes.is_empty() {
self.exhausted = true;
return None;
}
}
loop {
if self.level == self.classes.len() {
self.init_perms();
return Some(self.current_partition());
}
let have = if self.counts[self.level].is_none() {
self.first_composition(self.level);
true
} else {
self.next_composition(self.level)
};
let have = have && {
let mut ok = self.feasible_now();
while !ok {
if !self.next_composition(self.level) {
break;
}
ok = self.feasible_now();
}
ok
};
if have {
self.level += 1;
} else {
self.counts[self.level] = None;
if self.level == 0 {
self.exhausted = true;
return None;
}
self.level -= 1;
}
}
}
}
fn next_permutation(a: &mut [usize]) -> bool {
if a.len() < 2 {
return false;
}
let mut i = a.len() - 2;
loop {
if a[i] < a[i + 1] {
break;
}
if i == 0 {
return false;
}
i -= 1;
}
let mut j = a.len() - 1;
while a[j] <= a[i] {
j -= 1;
}
a.swap(i, j);
a[i + 1..].reverse();
true
}
#[cfg(test)]
mod tests {
use super::*;
use rbe::{MatchCond, Max, Pending, RbeStruct, SingleCond, rbe_error::RbeError};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
struct C(char);
impl std::fmt::Display for C {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl rbe::Key for C {}
impl rbe::Value for C {}
impl rbe::Ref for C {}
impl rbe::Context for C {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
enum TestKind {
Any,
Is(char),
}
impl MatchKind<C, C, C, C> for TestKind {
fn eval(&self, v: &C, _ctx: &C) -> Result<Pending<C, C, C>, RbeError<C, C, C, C, Self>> {
match self {
TestKind::Any => Ok(Pending::empty()),
TestKind::Is(expected) => {
if v.0 == *expected {
Ok(Pending::empty())
} else {
Err(RbeError::MsgError {
msg: format!("{v} != {expected}"),
})
}
},
}
}
}
type Cond = MatchCond<C, C, C, C, TestKind>;
type Table = RbeTable<C, C, C, C, TestKind>;
fn any(name: &str) -> Cond {
MatchCond::single(SingleCond::new().with_name(name).with_kind(TestKind::Any))
}
fn is(name: &str, expected: char) -> Cond {
MatchCond::single(SingleCond::new().with_name(name).with_kind(TestKind::Is(expected)))
}
fn buckets() -> HashMap<char, Vec<Table>> {
let mut ta = Table::new();
let c1 = ta.add_component(C('p'), &any("any_p"));
let c2 = ta.add_component(C('q'), &any("any_q"));
ta.with_rbe(RbeStruct::and(vec![
RbeStruct::symbol(c1, 1, Max::IntMax(1)),
RbeStruct::symbol(c2, 0, Max::IntMax(1)),
]));
let mut tb = Table::new();
let c3 = tb.add_component(C('p'), &is("is_a", 'a'));
tb.with_rbe(RbeStruct::symbol(c3, 1, Max::IntMax(1)));
HashMap::from([('A', vec![ta]), ('B', vec![tb])])
}
fn canonical(parts: &Partitions<char, C, C, C, C, TestKind>) -> Vec<(char, Vec<(char, char)>)> {
let mut result: Vec<(char, Vec<(char, char)>)> = parts
.iter()
.map(|(t, _, subset)| {
let mut vs: Vec<(char, char)> = subset.iter().map(|(k, v, _)| (k.0, v.0)).collect();
vs.sort_unstable();
(*t, vs)
})
.collect();
result.sort();
result
}
fn is_valid(parts: &Partitions<char, C, C, C, C, TestKind>) -> bool {
parts.iter().all(|(_, rbes, subset)| {
rbes.iter().all(|rbe| match rbe.matches(subset.clone()) {
Ok(iter) => iter.into_iter().any(|r| r.is_ok()),
Err(_) => false,
})
})
}
#[test]
fn differential_against_k_partitions() {
let exprs = buckets();
let neighs: Vec<(C, C, C)> = vec![
(C('p'), C('a'), C(' ')),
(C('p'), C('b'), C(' ')),
(C('q'), C('x'), C(' ')),
(C('z'), C('z'), C(' ')),
];
let new_parts: Vec<_> = class_partitions_iter(&neighs, &exprs).collect();
let old_parts: Vec<_> = crate::partitions_iter(&neighs, &exprs).collect();
let old_set: HashSet<_> = old_parts.iter().map(canonical).collect();
let new_set: HashSet<_> = new_parts.iter().map(canonical).collect();
for p in &new_set {
assert!(old_set.contains(p), "invented partition: {p:?}");
}
let old_valid: HashSet<_> = old_parts.iter().filter(|p| is_valid(p)).map(canonical).collect();
let new_valid: HashSet<_> = new_parts.iter().filter(|p| is_valid(p)).map(canonical).collect();
assert_eq!(old_valid, new_valid, "valid partitions must be preserved");
assert!(!new_valid.is_empty(), "the example admits a valid partition");
assert!(
new_parts.len() <= old_parts.len(),
"pruning must not enumerate more than the Cartesian space"
);
}
#[test]
fn refutes_without_enumeration() {
let exprs = buckets();
let neighs: Vec<(C, C, C)> = vec![(C('p'), C('b'), C(' ')), (C('q'), C('x'), C(' '))];
let new_parts: Vec<_> = class_partitions_iter(&neighs, &exprs).collect();
assert!(new_parts.is_empty(), "refuted upfront: B cannot be satisfied");
let old_valid = crate::partitions_iter(&neighs, &exprs).filter(is_valid).count();
assert_eq!(old_valid, 0);
}
#[test]
fn empty_neighbourhood() {
let exprs = buckets();
let neighs: Vec<(C, C, C)> = vec![];
let new_parts: Vec<_> = class_partitions_iter(&neighs, &exprs).collect();
assert!(new_parts.is_empty());
}
}