use crate::{Error, Reset};
pub struct Buckets<T> {
buckets: Vec<Vec<T>>,
touched: Vec<usize>,
}
impl<T> Default for Buckets<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Buckets<T> {
pub fn new() -> Self {
Self {
buckets: Vec::new(),
touched: Vec::new(),
}
}
pub fn with_buckets(count: usize) -> Self {
let mut b = Self {
buckets: Vec::with_capacity(count),
touched: Vec::new(),
};
b.resize_buckets(count);
b
}
pub fn len(&self) -> usize {
self.buckets.len()
}
pub fn is_empty(&self) -> bool {
self.buckets.is_empty()
}
pub fn is_updated(&self) -> bool {
!self.touched.is_empty()
}
pub fn push_bucket(&mut self) {
self.buckets.push(Vec::new());
}
pub fn resize_buckets(&mut self, count: usize) {
self.buckets.resize_with(count, Vec::new);
self.touched.clear();
}
pub fn get(&self, index: usize) -> Option<&Vec<T>> {
self.buckets.get(index)
}
pub fn is_touched(&self, index: usize) -> bool {
self.touched.contains(&index)
}
pub fn push(&mut self, index: usize, value: T) -> Result<(), T> {
let Some(bucket) = self.buckets.get_mut(index) else {
return Err(value);
};
if bucket.is_empty() {
self.touched.push(index);
}
bucket.push(value);
Ok(())
}
pub fn iter_updated(&self) -> impl Iterator<Item = (usize, &Vec<T>)> {
self.touched
.iter()
.filter_map(move |&i| self.buckets.get(i).map(|b| (i, b)))
}
}
impl<T> Reset for Buckets<T> {
type Error = Error;
fn reset(&mut self) -> Result<(), Self::Error> {
for index in self.touched.drain(..) {
if let Some(bucket) = self.buckets.get_mut(index) {
bucket.clear();
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_is_empty() {
let b: Buckets<i32> = Buckets::new();
assert_eq!(b.len(), 0);
assert!(b.is_empty());
assert!(!b.is_updated());
}
#[test]
fn with_buckets_starts_clean() {
let b: Buckets<i32> = Buckets::with_buckets(3);
assert_eq!(b.len(), 3);
assert!(!b.is_updated());
assert!(!b.is_touched(0));
assert_eq!(b.iter_updated().count(), 0);
}
#[test]
fn push_marks_touched_and_appends() {
let mut b = Buckets::with_buckets(3);
b.push(0, 10).unwrap();
b.push(2, 20).unwrap();
b.push(0, 11).unwrap();
assert!(b.is_touched(0));
assert!(!b.is_touched(1));
assert!(b.is_touched(2));
let collected: Vec<(usize, Vec<i32>)> =
b.iter_updated().map(|(i, v)| (i, v.clone())).collect();
assert_eq!(collected, vec![(0, vec![10, 11]), (2, vec![20])]);
}
#[test]
fn push_out_of_bounds_returns_err_with_value() {
let mut b: Buckets<i32> = Buckets::with_buckets(2);
let err = b.push(5, 99).unwrap_err();
assert_eq!(err, 99);
assert!(!b.is_updated());
}
#[test]
fn reset_clears_only_touched_preserves_capacity() -> Result<(), Error> {
let mut b: Buckets<i32> = Buckets::with_buckets(3);
b.push(0, 10).unwrap();
b.push(2, 20).unwrap();
let cap_before_0 = b.get(0).unwrap().capacity();
let cap_before_2 = b.get(2).unwrap().capacity();
b.reset()?;
assert!(!b.is_updated());
assert_eq!(b.get(0).unwrap(), &Vec::<i32>::new());
assert_eq!(b.get(1).unwrap(), &Vec::<i32>::new());
assert_eq!(b.get(2).unwrap(), &Vec::<i32>::new());
assert_eq!(b.iter_updated().count(), 0);
assert!(b.get(0).unwrap().capacity() >= cap_before_0);
assert!(b.get(2).unwrap().capacity() >= cap_before_2);
b.push(2, 30).unwrap();
let collected: Vec<(usize, Vec<i32>)> =
b.iter_updated().map(|(i, v)| (i, v.clone())).collect();
assert_eq!(collected, vec![(2, vec![30])]);
Ok(())
}
#[test]
fn resize_preserves_retained_data_and_clears_dirty() {
let mut b = Buckets::with_buckets(2);
b.push(1, 20).unwrap();
b.resize_buckets(4);
assert_eq!(b.len(), 4);
assert_eq!(b.get(1).unwrap(), &vec![20]);
assert!(!b.is_updated());
b.resize_buckets(1);
assert_eq!(b.len(), 1);
assert!(!b.is_updated());
}
#[test]
fn second_push_into_touched_bucket_does_not_double_count() {
let mut b = Buckets::with_buckets(2);
b.push(0, 1).unwrap();
b.push(0, 2).unwrap();
b.push(0, 3).unwrap();
assert_eq!(b.iter_updated().count(), 1);
let (idx, vec) = b.iter_updated().next().unwrap();
assert_eq!(idx, 0);
assert_eq!(vec, &vec![1, 2, 3]);
}
}