use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::row_locator::RowLocator;
const BLOCK: usize = 256;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PostingList {
frozen: Vec<Arc<[RowLocator]>>,
tail: Vec<RowLocator>,
}
impl PostingList {
#[must_use]
pub const fn new() -> Self {
Self {
frozen: Vec::new(),
tail: Vec::new(),
}
}
#[must_use]
pub fn single(locator: RowLocator) -> Self {
Self {
frozen: Vec::new(),
tail: alloc::vec![locator],
}
}
pub fn push(&mut self, locator: RowLocator) {
self.tail.push(locator);
if self.tail.len() >= BLOCK {
let full = core::mem::take(&mut self.tail);
self.frozen.push(Arc::from(full.into_boxed_slice()));
}
}
#[must_use]
pub fn len(&self) -> usize {
self.frozen.len() * BLOCK + self.tail.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.frozen.is_empty() && self.tail.is_empty()
}
#[must_use]
pub fn iter(&self) -> Iter<'_> {
Iter {
list: self,
block: 0,
pos: 0,
}
}
pub fn iter_copied(&self) -> impl Iterator<Item = RowLocator> + '_ {
self.iter().copied()
}
#[must_use]
pub fn first(&self) -> Option<RowLocator> {
self.frozen
.first()
.and_then(|b| b.first().copied())
.or_else(|| self.tail.first().copied())
}
#[must_use]
pub fn last(&self) -> Option<RowLocator> {
self.tail
.last()
.copied()
.or_else(|| self.frozen.last().and_then(|b| b.last().copied()))
}
#[must_use]
pub fn contains(&self, locator: RowLocator) -> bool {
self.iter().any(|l| *l == locator)
}
pub fn retain(&mut self, keep: impl Fn(RowLocator) -> bool) {
if self.iter().all(|l| keep(*l)) {
return;
}
let kept: Self = self.iter().copied().filter(|l| keep(*l)).collect();
*self = kept;
}
#[must_use]
pub fn to_vec(&self) -> Vec<RowLocator> {
let mut out = Vec::with_capacity(self.len());
out.extend(self.iter().copied());
out
}
}
#[derive(Debug)]
pub struct Iter<'a> {
list: &'a PostingList,
block: usize,
pos: usize,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a RowLocator;
fn next(&mut self) -> Option<&'a RowLocator> {
while self.block < self.list.frozen.len() {
let block = &self.list.frozen[self.block];
if let Some(locator) = block.get(self.pos) {
self.pos += 1;
return Some(locator);
}
self.block += 1;
self.pos = 0;
}
let locator = self.list.tail.get(self.pos)?;
self.pos += 1;
Some(locator)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let seen = self.block * BLOCK + self.pos;
let left = self.list.len().saturating_sub(seen);
(left, Some(left))
}
}
impl ExactSizeIterator for Iter<'_> {}
impl FromIterator<RowLocator> for PostingList {
fn from_iter<I: IntoIterator<Item = RowLocator>>(iter: I) -> Self {
let mut out = Self::new();
for locator in iter {
out.push(locator);
}
out
}
}
impl PartialEq<[RowLocator]> for PostingList {
fn eq(&self, other: &[RowLocator]) -> bool {
self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a == b)
}
}
impl<const N: usize> PartialEq<[RowLocator; N]> for PostingList {
fn eq(&self, other: &[RowLocator; N]) -> bool {
*self == other[..]
}
}
impl From<Vec<RowLocator>> for PostingList {
fn from(v: Vec<RowLocator>) -> Self {
v.into_iter().collect()
}
}
impl<'a> IntoIterator for &'a PostingList {
type Item = &'a RowLocator;
type IntoIter = alloc::boxed::Box<dyn Iterator<Item = &'a RowLocator> + 'a>;
fn into_iter(self) -> Self::IntoIter {
alloc::boxed::Box::new(self.iter())
}
}
#[cfg(test)]
mod tests {
use super::{BLOCK, PostingList};
use crate::row_locator::RowLocator;
fn hot(i: usize) -> RowLocator {
RowLocator::Hot(i)
}
#[test]
fn empty_list_allocates_nothing_and_reads_empty() {
let list = PostingList::new();
assert!(list.is_empty());
assert_eq!(list.len(), 0);
assert_eq!(list.iter().count(), 0);
assert_eq!(list.last(), None);
}
#[test]
fn order_and_length_survive_block_boundaries() {
let n = BLOCK * 3 + 7;
let list: PostingList = (0..n).map(hot).collect();
assert_eq!(list.len(), n);
let read: alloc::vec::Vec<_> = list.iter().copied().collect();
assert_eq!(read, (0..n).map(hot).collect::<alloc::vec::Vec<_>>());
assert_eq!(list.last(), Some(hot(n - 1)));
}
#[test]
fn length_is_exact_at_a_block_boundary() {
let list: PostingList = (0..BLOCK).map(hot).collect();
assert_eq!(list.len(), BLOCK);
assert_eq!(list.iter().count(), BLOCK);
assert_eq!(list.last(), Some(hot(BLOCK - 1)));
}
#[test]
fn a_clone_does_not_see_later_appends() {
let mut original: PostingList = (0..BLOCK * 2).map(hot).collect();
let snapshot = original.clone();
original.push(hot(9999));
assert_eq!(snapshot.len(), BLOCK * 2);
assert_eq!(original.len(), BLOCK * 2 + 1);
assert_eq!(snapshot.last(), Some(hot(BLOCK * 2 - 1)));
}
#[test]
fn retain_rebuilds_across_blocks() {
let mut list: PostingList = (0..BLOCK * 2 + 5).map(hot).collect();
list.retain(|l| matches!(l, RowLocator::Hot(i) if i % 2 == 0));
let read: alloc::vec::Vec<_> = list.iter().copied().collect();
let want: alloc::vec::Vec<_> = (0..BLOCK * 2 + 5).filter(|i| i % 2 == 0).map(hot).collect();
assert_eq!(read, want);
assert_eq!(list.len(), want.len());
}
#[test]
fn retain_keeping_everything_leaves_the_list_alone() {
let list: PostingList = (0..BLOCK + 3).map(hot).collect();
let mut same = list.clone();
same.retain(|_| true);
assert_eq!(same, list);
}
#[test]
fn retain_can_empty_the_list() {
let mut list: PostingList = (0..BLOCK + 3).map(hot).collect();
list.retain(|_| false);
assert!(list.is_empty());
assert_eq!(list.len(), 0);
}
}