use crate::core::entangled::{hash_u64, EntangledHVec};
fn item_key(item: &EntangledHVec) -> u64 {
item.indices()
.iter()
.fold(0x5B17_5E7Cu64, |h, &i| hash_u64(h, i as u64))
}
pub fn subset_indices(item: &EntangledHVec, k: usize) -> Vec<u32> {
let idx = item.indices();
if k >= idx.len() {
return idx.to_vec();
}
let key = item_key(item);
let mut ranked: Vec<(u64, u32)> = idx.iter().map(|&i| (hash_u64(key, i as u64), i)).collect();
ranked.sort_unstable();
ranked.truncate(k);
let mut out: Vec<u32> = ranked.into_iter().map(|(_, i)| i).collect();
out.sort_unstable();
out
}
pub fn bundle_subset<V: std::borrow::Borrow<EntangledHVec>>(
items: &[V],
k: usize,
) -> EntangledHVec {
let Some(first) = items.first() else {
return EntangledHVec::from_indices(Vec::new(), 0);
};
let dim = first.borrow().dim;
let mut all: Vec<u32> = items
.iter()
.flat_map(|v| subset_indices(v.borrow(), k))
.collect();
all.sort_unstable();
all.dedup();
EntangledHVec::from_indices(all, dim)
}
pub fn contains_subset(bundle: &EntangledHVec, item: &EntangledHVec, k: usize) -> bool {
let b = bundle.indices();
subset_indices(item, k)
.iter()
.all(|i| b.binary_search(i).is_ok())
}
#[cfg(test)]
mod tests {
use super::*;
const D: usize = 16384;
fn item(seed: u64) -> EntangledHVec {
EntangledHVec::new_with_density(D, 256, seed)
}
#[test]
fn selection_is_deterministic_and_a_subset() {
let a = item(7);
let s1 = subset_indices(&a, 11);
let s2 = subset_indices(&a.clone(), 11);
assert_eq!(s1, s2);
assert_eq!(s1.len(), 11);
assert!(s1.windows(2).all(|w| w[0] < w[1]));
assert!(s1.iter().all(|i| a.indices().binary_search(i).is_ok()));
}
#[test]
fn every_inserted_member_tests_positive() {
let items: Vec<_> = (0..200).map(item).collect();
for k in [4, 11, 64] {
let b = bundle_subset(&items, k);
assert!(items.iter().all(|it| contains_subset(&b, it, k)));
}
}
#[test]
fn subset_size_clamps_at_item_size() {
let a = item(3);
let n = a.indices().len();
assert_eq!(subset_indices(&a, n), a.indices());
assert_eq!(subset_indices(&a, n + 100), a.indices());
assert_eq!(subset_indices(&a, n - 1).len(), n - 1);
assert!(subset_indices(&a, 0).is_empty());
}
}