use crate::graph::NodeId;
pub(crate) fn words_for_n(n: usize) -> usize {
n.div_ceil(64)
}
pub(crate) fn words_to_sorted_nodes(words: &[u64]) -> Vec<NodeId> {
let mut out = Vec::with_capacity(words.iter().map(|w| w.count_ones() as usize).sum());
for (w, &word) in words.iter().enumerate() {
let mut bits = word;
while bits != 0 {
let b = bits.trailing_zeros() as usize;
out.push(NodeId((w * 64 + b) as u32));
bits &= bits - 1; }
}
out
}
#[derive(Clone, Debug)]
pub(crate) struct LargeCoalition {
words: Vec<u64>,
n: usize,
}
impl LargeCoalition {
pub(crate) fn empty(n_nodes: usize) -> Self {
Self {
words: vec![0u64; words_for_n(n_nodes)],
n: n_nodes,
}
}
pub(crate) fn clear(&mut self) {
self.words.fill(0);
}
pub(crate) fn insert(&mut self, node: NodeId) {
let i = node.0 as usize;
debug_assert!(
i < self.n,
"NodeId {i} out of range for coalition sized {}",
self.n
);
self.words[i / 64] |= 1u64 << (i % 64);
debug_assert!(self.contains(node), "insert did not set bit for {node:?}");
}
pub(crate) fn contains(&self, node: NodeId) -> bool {
let i = node.0 as usize;
(self.words[i / 64] >> (i % 64)) & 1 == 1
}
pub(crate) fn to_sorted_nodes(&self) -> Vec<NodeId> {
let mut out = Vec::with_capacity(self.count());
for (w, &word) in self.words.iter().enumerate() {
let mut bits = word;
while bits != 0 {
let b = bits.trailing_zeros() as usize;
out.push(NodeId((w * 64 + b) as u32));
bits &= bits - 1; }
}
out
}
pub(crate) fn snapshot_key(&self) -> Box<[u64]> {
self.words.clone().into_boxed_slice()
}
pub(crate) fn words(&self) -> &[u64] {
&self.words
}
pub(crate) fn count(&self) -> usize {
self.words.iter().map(|w| w.count_ones() as usize).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_has_no_nodes() {
let c = LargeCoalition::empty(130);
assert_eq!(c.count(), 0);
assert!(c.to_sorted_nodes().is_empty());
assert!(!c.contains(NodeId(0)));
assert!(!c.contains(NodeId(129)));
}
#[test]
fn insert_spans_multiple_words() {
let mut c = LargeCoalition::empty(130);
c.insert(NodeId(0));
c.insert(NodeId(64));
c.insert(NodeId(129));
assert_eq!(c.count(), 3);
assert!(c.contains(NodeId(0)));
assert!(c.contains(NodeId(64)));
assert!(c.contains(NodeId(129)));
assert!(!c.contains(NodeId(63)));
assert_eq!(
c.to_sorted_nodes(),
vec![NodeId(0), NodeId(64), NodeId(129)]
);
}
#[test]
fn clear_resets_to_empty_without_reallocating() {
let mut c = LargeCoalition::empty(130);
c.insert(NodeId(0));
c.insert(NodeId(129));
let words_ptr_before = c.words().as_ptr();
c.clear();
assert_eq!(c.count(), 0);
assert!(c.to_sorted_nodes().is_empty());
assert!(!c.contains(NodeId(0)) && !c.contains(NodeId(129)));
assert_eq!(
c.words().as_ptr(),
words_ptr_before,
"clear() must reuse the existing allocation, not reallocate"
);
}
#[test]
fn to_sorted_nodes_is_ascending_regardless_of_insert_order() {
let mut c = LargeCoalition::empty(200);
for &i in &[150u32, 3, 70, 199, 0, 65] {
c.insert(NodeId(i));
}
let nodes = c.to_sorted_nodes();
let mut sorted = nodes.clone();
sorted.sort_unstable();
assert_eq!(nodes, sorted, "to_sorted_nodes must already be ascending");
}
#[test]
fn snapshot_key_matches_words_and_is_usable_as_hashmap_key() {
use std::collections::HashMap;
let mut c = LargeCoalition::empty(100);
c.insert(NodeId(5));
c.insert(NodeId(80));
let key = c.snapshot_key();
assert_eq!(&*key, c.words());
let mut map: HashMap<Box<[u64]>, f64> = HashMap::new();
map.insert(key, 42.0);
assert_eq!(map.get(c.words()), Some(&42.0));
}
#[test]
fn words_for_n_rounds_up() {
assert_eq!(words_for_n(1), 1);
assert_eq!(words_for_n(64), 1);
assert_eq!(words_for_n(65), 2);
assert_eq!(words_for_n(128), 2);
assert_eq!(words_for_n(129), 3);
assert_eq!(words_for_n(256), 4);
}
}