use crate::Vec;
const BLOCK_LEN: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct AdaptivePackedU32 {
bases: Vec<u32>,
word_offsets: Vec<u32>,
widths: Vec<u8>,
words: Vec<u64>,
len: usize,
}
impl AdaptivePackedU32 {
pub(super) fn estimated_storage_bytes(values: &[u32]) -> usize {
let blocks = values.len().div_ceil(BLOCK_LEN);
let words = values
.chunks(BLOCK_LEN)
.map(|block| words_for(block.len(), block_width(block)))
.sum::<usize>();
blocks * (size_of::<u32>() + size_of::<u32>() + size_of::<u8>())
+ size_of::<u32>()
+ words * size_of::<u64>()
}
pub(super) fn try_from_values(values: &[u32]) -> Option<Self> {
let blocks = values.len().div_ceil(BLOCK_LEN);
let mut bases = Vec::with_capacity(blocks);
let mut word_offsets = Vec::<u32>::with_capacity(blocks + 1);
let mut widths = Vec::with_capacity(blocks);
word_offsets.push(0);
for block in values.chunks(BLOCK_LEN) {
let minimum = block.iter().min().copied()?;
let maximum = block.iter().max().copied()?;
let bits = value_bits(maximum - minimum);
bases.push(minimum);
widths.push(bits);
let words = u32::try_from(words_for(block.len(), bits)).ok()?;
let next = word_offsets.last().copied()?.checked_add(words)?;
word_offsets.push(next);
}
let word_count = word_offsets.last().copied()?;
let mut words = vec![0_u64; word_count as usize];
for (block_index, values) in values.chunks(BLOCK_LEN).enumerate() {
let start = word_offsets[block_index] as usize;
let end = word_offsets[block_index + 1] as usize;
fill(
&mut words[start..end],
values,
bases[block_index],
widths[block_index],
);
}
Some(Self {
bases,
word_offsets,
widths,
words,
len: values.len(),
})
}
#[inline]
pub(super) fn get(&self, index: usize) -> u32 {
debug_assert!(index < self.len);
let block = index / BLOCK_LEN;
let within = index % BLOCK_LEN;
self.bases[block] + self.read(block, within)
}
#[inline]
pub(super) fn for_each(&self, start: usize, end: usize, mut visit: impl FnMut(u32)) {
debug_assert!(start <= end && end <= self.len);
let mut index = start;
while index < end {
let block = index / BLOCK_LEN;
let block_end = end.min((block + 1) * BLOCK_LEN);
let base = self.bases[block];
let mut within = index % BLOCK_LEN;
while index < block_end {
visit(base + self.read(block, within));
within += 1;
index += 1;
}
}
}
pub(super) fn storage_bytes(&self) -> usize {
self.bases.len() * size_of::<u32>()
+ self.word_offsets.len() * size_of::<u32>()
+ self.widths.len() * size_of::<u8>()
+ self.words.len() * size_of::<u64>()
}
#[inline]
fn read(&self, block: usize, within: usize) -> u32 {
let bits = self.widths[block];
if bits == 0 {
return 0;
}
let words =
&self.words[self.word_offsets[block] as usize..self.word_offsets[block + 1] as usize];
read(words, within * usize::from(bits), bits)
}
}
fn fill(words: &mut [u64], values: &[u32], base: u32, bits: u8) {
for (index, &value) in values.iter().enumerate() {
let bit = index * usize::from(bits);
write(words, bit, u64::from(value - base), bits);
}
}
fn write(words: &mut [u64], bit: usize, value: u64, bits: u8) {
if bits == 0 {
return;
}
let word = bit / 64;
let offset = bit % 64;
words[word] |= value << offset;
if offset + usize::from(bits) > 64 {
words[word + 1] |= value >> (64 - offset);
}
}
fn read(words: &[u64], bit: usize, bits: u8) -> u32 {
let word = bit / 64;
let offset = bit % 64;
let mut value = words[word] >> offset;
if offset + usize::from(bits) > 64 {
value |= words[word + 1] << (64 - offset);
}
u32::try_from(value & mask(bits)).unwrap_or(u32::MAX)
}
fn block_width(values: &[u32]) -> u8 {
let Some((&minimum, &maximum)) = values.iter().min().zip(values.iter().max()) else {
return 0;
};
value_bits(maximum - minimum)
}
fn value_bits(maximum: u32) -> u8 {
u8::try_from(u32::BITS - maximum.leading_zeros()).unwrap_or(32)
}
fn words_for(values: usize, bits: u8) -> usize {
(values * usize::from(bits)).div_ceil(64)
}
const fn mask(bits: u8) -> u64 {
if bits == 0 { 0 } else { (1_u64 << bits) - 1 }
}