use rudb_common::{Error, Result};
const SUPERBLOCK_BITS: usize = 4096;
const BLOCK_BITS: usize = 512;
const BLOCKS_PER_SUPERBLOCK: usize = SUPERBLOCK_BITS / BLOCK_BITS;
const BLOCK_WORDS: usize = BLOCK_BITS / 64;
const SAMPLE: u64 = 4096;
#[derive(Debug, Clone)]
pub(crate) struct Rank {
superblocks: Vec<u32>,
blocks: Vec<u16>,
}
impl Rank {
pub(crate) fn build(bits: &[u64]) -> Self {
let blocks = bits.len().div_ceil(BLOCK_WORDS);
let mut index = Self {
superblocks: Vec::with_capacity(blocks.div_ceil(BLOCKS_PER_SUPERBLOCK)),
blocks: Vec::with_capacity(blocks),
};
let mut total = 0_u32;
let mut within = 0_u16;
for block in 0..blocks {
if block % BLOCKS_PER_SUPERBLOCK == 0 {
index.superblocks.push(total);
within = 0;
}
index.blocks.push(within);
let words = block * BLOCK_WORDS;
let ones: u32 = bits[words..(words + BLOCK_WORDS).min(bits.len())]
.iter()
.map(|word| word.count_ones())
.sum();
total += ones;
#[expect(
clippy::cast_possible_truncation,
reason = "a superblock holds at most 4096 bits, which fits a u16"
)]
let ones = ones as u16;
within += ones;
}
index
}
pub(crate) fn rank(&self, bits: &[u64], at: usize) -> u64 {
let block = at / BLOCK_BITS;
let superblock = block / BLOCKS_PER_SUPERBLOCK;
let mut count = u64::from(self.superblocks[superblock]) + u64::from(self.blocks[block]);
let from = block * BLOCK_WORDS;
let word = at / 64;
for whole in &bits[from..word] {
count += u64::from(whole.count_ones());
}
let remainder = at % 64;
if remainder != 0 {
let mask = (1_u64 << remainder) - 1;
count += u64::from((bits[word] & mask).count_ones());
}
count
}
pub(crate) fn bytes(&self) -> usize {
self.superblocks.len() * size_of::<u32>() + self.blocks.len() * size_of::<u16>()
}
pub(crate) fn shape(words: usize) -> (usize, usize) {
let blocks = words.div_ceil(BLOCK_WORDS);
(blocks, blocks.div_ceil(BLOCKS_PER_SUPERBLOCK))
}
pub(crate) fn write(&self, out: &mut Vec<u8>) {
for count in &self.superblocks {
out.extend_from_slice(&count.to_le_bytes());
}
for offset in &self.blocks {
out.extend_from_slice(&offset.to_le_bytes());
}
}
pub(crate) fn read(bytes: &[u8], words: usize) -> Result<Self> {
let (blocks, superblocks) = Self::shape(words);
let split = superblocks * size_of::<u32>();
if bytes.len() != split + blocks * size_of::<u16>() {
return Err(malformed(
"a dense key map's rank index is not the size its range implies",
));
}
Ok(Self {
superblocks: bytes[..split]
.chunks_exact(size_of::<u32>())
.map(|word| u32::from_le_bytes(word.try_into().expect("four bytes")))
.collect(),
blocks: bytes[split..]
.chunks_exact(size_of::<u16>())
.map(|word| u16::from_le_bytes(word.try_into().expect("two bytes")))
.collect(),
})
}
fn ones_before_superblock(&self, superblock: usize) -> u64 {
u64::from(self.superblocks[superblock])
}
fn ones_before_block(&self, block: usize) -> u64 {
u64::from(self.superblocks[block / BLOCKS_PER_SUPERBLOCK]) + u64::from(self.blocks[block])
}
}
#[derive(Debug, Clone)]
pub struct BitVector {
words: Vec<u64>,
len: usize,
ones: u64,
rank: Rank,
ones_sample: Vec<u32>,
zeros_sample: Vec<u32>,
}
impl BitVector {
pub fn new(words: Vec<u64>, len: usize) -> Result<Self> {
if words.len() != len.div_ceil(64) {
return Err(malformed("a bit vector's words do not match its length"));
}
let tail = len % 64;
if tail != 0 && words[len / 64] >> tail != 0 {
return Err(malformed("a bit vector has bits set past its length"));
}
let rank = Rank::build(&words);
let ones = words.iter().map(|word| u64::from(word.count_ones())).sum();
let mut vector =
Self { words, len, ones, rank, ones_sample: Vec::new(), zeros_sample: Vec::new() };
vector.sample();
Ok(vector)
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn ones(&self) -> u64 {
self.ones
}
#[must_use]
pub fn zeros(&self) -> u64 {
bits(self.len) - self.ones
}
#[must_use]
pub fn rank1(&self, at: usize) -> u64 {
if at >= self.len {
return self.ones;
}
self.rank.rank(&self.words, at)
}
#[must_use]
pub fn rank0(&self, at: usize) -> u64 {
let at = at.min(self.len);
bits(at) - self.rank1(at)
}
#[must_use]
pub fn select1(&self, nth: u64) -> Option<usize> {
if nth >= self.ones {
return None;
}
Some(self.select(nth, true))
}
#[must_use]
pub fn select0(&self, nth: u64) -> Option<usize> {
if nth >= self.zeros() {
return None;
}
Some(self.select(nth, false))
}
pub(crate) fn words(&self) -> &[u64] {
&self.words
}
#[must_use]
pub fn bytes(&self) -> usize {
self.words.len() * size_of::<u64>() + self.rank.bytes()
}
pub fn write(&self, out: &mut Vec<u8>) {
for word in &self.words {
out.extend_from_slice(&word.to_le_bytes());
}
self.rank.write(out);
}
pub fn read(bytes: &[u8], len: usize) -> Result<Self> {
let words = len.div_ceil(64);
let bitmap = words * size_of::<u64>();
if bytes.len() < bitmap {
return Err(malformed("a bit vector is shorter than its length implies"));
}
let held = bytes[..bitmap]
.chunks_exact(size_of::<u64>())
.map(|word| u64::from_le_bytes(word.try_into().expect("eight bytes")))
.collect::<Vec<u64>>();
let rank = Rank::read(&bytes[bitmap..], words)?;
let tail = len % 64;
if tail != 0 && held[len / 64] >> tail != 0 {
return Err(malformed("a bit vector has bits set past its length"));
}
let ones = held.iter().map(|word| u64::from(word.count_ones())).sum();
let mut vector = Self {
words: held,
len,
ones,
rank,
ones_sample: Vec::new(),
zeros_sample: Vec::new(),
};
vector.sample();
Ok(vector)
}
fn sample(&mut self) {
self.ones_sample = self.samples(true, self.ones);
self.zeros_sample = self.samples(false, self.zeros());
}
fn samples(&self, set: bool, total: u64) -> Vec<u32> {
let superblocks = self.rank.superblocks.len();
let mut sample = Vec::with_capacity(usize::try_from(total.div_ceil(SAMPLE)).unwrap_or(0));
let mut at = 0_usize;
for group in 0..total.div_ceil(SAMPLE) {
let target = group * SAMPLE;
while at + 1 < superblocks && self.before(set, at + 1) <= target {
at += 1;
}
sample.push(u32::try_from(at).unwrap_or(u32::MAX));
}
sample
}
fn before(&self, set: bool, superblock: usize) -> u64 {
let ones = self.rank.ones_before_superblock(superblock);
if set { ones } else { bits(superblock * SUPERBLOCK_BITS) - ones }
}
fn select(&self, nth: u64, set: bool) -> usize {
if self.rank.superblocks.is_empty() {
return self.len;
}
let samples = if set { &self.ones_sample } else { &self.zeros_sample };
let last = self.rank.superblocks.len() - 1;
let group = usize::try_from(nth / SAMPLE).unwrap_or(usize::MAX);
let from = samples.get(group).map_or(0, |at| *at as usize);
let to = samples.get(group + 1).map_or(last, |at| *at as usize);
let (mut low, mut high) = (from, to);
while low < high {
let middle = low + (high - low).div_ceil(2);
if self.before(set, middle) <= nth {
low = middle;
} else {
high = middle - 1;
}
}
let superblock = low;
let blocks = self.rank.blocks.len();
let first = superblock * BLOCKS_PER_SUPERBLOCK;
let within = |block: usize| -> u64 {
let ones = self.rank.ones_before_block(block);
if set { ones } else { bits(block * BLOCK_BITS) - ones }
};
let mut block = first;
for candidate in first..(first + BLOCKS_PER_SUPERBLOCK).min(blocks) {
if within(candidate) <= nth {
block = candidate;
} else {
break;
}
}
let mut before = within(block);
for word in block * BLOCK_WORDS..self.words.len() {
let held = if set { self.words[word] } else { !self.words[word] };
let here = u64::from(held.count_ones());
if before + here > nth {
#[expect(
clippy::cast_possible_truncation,
reason = "a word holds at most 64 bits, so the offset within it fits a u32"
)]
let offset = (nth - before) as u32;
return word * 64 + nth_set(held, offset) as usize;
}
before += here;
}
self.len
}
}
fn bits(at: usize) -> u64 {
u64::try_from(at).unwrap_or(u64::MAX)
}
fn nth_set(mut word: u64, nth: u32) -> u32 {
for _ in 0..nth {
word &= word - 1;
}
word.trailing_zeros()
}
fn malformed(message: impl Into<String>) -> Error {
Error::invalid_input(format!("invalid rudb bit vector: {}", message.into()))
}
#[cfg(test)]
mod tests {
use super::*;
fn vector(bits: &[bool]) -> BitVector {
let mut words = vec![0_u64; bits.len().div_ceil(64)];
for (at, bit) in bits.iter().enumerate() {
if *bit {
words[at / 64] |= 1 << (at % 64);
}
}
BitVector::new(words, bits.len()).expect("build")
}
fn agrees(bits: &[bool]) {
let built = vector(bits);
let (mut ones, mut zeros) = (Vec::new(), Vec::new());
for (at, bit) in bits.iter().enumerate() {
assert_eq!(built.rank1(at), ones.len() as u64, "rank1 at {at}");
assert_eq!(built.rank0(at), zeros.len() as u64, "rank0 at {at}");
if *bit { ones.push(at) } else { zeros.push(at) }
}
assert_eq!(built.ones(), ones.len() as u64);
assert_eq!(built.zeros(), zeros.len() as u64);
for (nth, at) in ones.iter().enumerate() {
assert_eq!(built.select1(nth as u64), Some(*at), "select1 of {nth}");
}
for (nth, at) in zeros.iter().enumerate() {
assert_eq!(built.select0(nth as u64), Some(*at), "select0 of {nth}");
}
assert_eq!(built.select1(ones.len() as u64), None, "there is no one past the last");
assert_eq!(built.select0(zeros.len() as u64), None, "there is no zero past the last");
}
#[test]
fn an_empty_vector_answers_nothing_rather_than_panicking() {
let built = vector(&[]);
assert!(built.is_empty());
assert_eq!(built.ones(), 0);
assert_eq!(built.zeros(), 0);
assert_eq!(built.select1(0), None);
assert_eq!(built.select0(0), None);
assert_eq!(built.rank1(0), 0);
assert_eq!(built.rank0(9), 0);
}
#[test]
fn a_vector_of_one_bit_each_way_agrees_with_counting() {
agrees(&[true]);
agrees(&[false]);
}
#[test]
fn alternating_bits_agree_with_counting_across_a_word_boundary() {
agrees(&(0..200).map(|at| at % 2 == 0).collect::<Vec<bool>>());
}
#[test]
fn a_vector_longer_than_a_superblock_agrees_with_counting() {
agrees(&(0..10_000).map(|at| at % 7 == 0).collect::<Vec<bool>>());
}
#[test]
fn a_vector_that_is_almost_all_ones_agrees_with_counting() {
agrees(&(0..20_000).map(|at| at % 1000 != 0).collect::<Vec<bool>>());
}
#[test]
fn a_vector_that_is_almost_all_zeros_agrees_with_counting() {
agrees(&(0..20_000).map(|at| at % 1000 == 0).collect::<Vec<bool>>());
}
#[test]
fn a_vector_of_all_ones_and_one_of_all_zeros_both_agree() {
agrees(&vec![true; 5000]);
agrees(&vec![false; 5000]);
}
#[test]
fn a_run_of_ones_longer_than_the_select_sample_is_found() {
let mut bits = vec![false; 3];
bits.extend(std::iter::repeat_n(true, 9000));
bits.push(false);
agrees(&bits);
}
#[test]
fn rank_past_the_end_saturates_rather_than_reading_past_it() {
let built = vector(&[true, false, true]);
assert_eq!(built.rank1(3), 2);
assert_eq!(built.rank1(9999), 2);
assert_eq!(built.rank0(9999), 1);
}
#[test]
fn a_vector_survives_being_written_and_read_back() {
let bits = (0..5000).map(|at| at % 3 == 0).collect::<Vec<bool>>();
let built = vector(&bits);
let mut bytes = Vec::new();
built.write(&mut bytes);
assert_eq!(bytes.len(), built.bytes(), "bytes() is what write() writes");
let read = BitVector::read(&bytes, bits.len()).expect("read");
assert_eq!(read.ones(), built.ones());
for nth in 0..read.ones() {
assert_eq!(read.select1(nth), built.select1(nth));
}
for nth in 0..read.zeros() {
assert_eq!(read.select0(nth), built.select0(nth));
}
}
#[test]
fn a_word_count_that_does_not_match_the_length_is_refused() {
assert!(BitVector::new(vec![0; 2], 64).is_err());
assert!(BitVector::new(vec![0; 1], 65).is_err());
}
#[test]
fn a_bit_set_past_the_length_is_refused_rather_than_counted() {
assert!(BitVector::new(vec![1 << 40], 8).is_err());
let mut bytes = Vec::new();
vector(&[true, false, true]).write(&mut bytes);
bytes[0] |= 1 << 4;
assert!(BitVector::read(&bytes, 3).is_err());
}
#[test]
fn a_truncated_vector_is_refused_rather_than_read_past() {
let mut bytes = Vec::new();
vector(&(0..5000).map(|at| at % 3 == 0).collect::<Vec<bool>>()).write(&mut bytes);
assert!(BitVector::read(&bytes[..bytes.len() - 1], 5000).is_err());
assert!(BitVector::read(&bytes[..4], 5000).is_err());
}
#[test]
fn the_rank_index_costs_about_an_eighth_of_the_bitmap() {
let built = vector(&(0..1_000_000).map(|at| at % 5 == 0).collect::<Vec<bool>>());
let bitmap = 1_000_000 / 8;
assert!(built.bytes() > bitmap, "{} is not more than {bitmap}", built.bytes());
assert!(
built.bytes() < bitmap * 6 / 5,
"{} is more than a fifth over {bitmap}",
built.bytes()
);
}
}