use crate::common::types::PointOffsetType;
#[derive(Debug, Clone)]
enum PointValueEntry<N> {
Single(N),
Slice { start: u32, count: u32 },
}
impl<N> Default for PointValueEntry<N> {
fn default() -> Self {
PointValueEntry::Slice { start: 0, count: 0 }
}
}
#[derive(Debug, Clone, Default)]
pub struct ImmutablePointToValues<N: Default> {
point_entries: Vec<PointValueEntry<N>>,
values_container: Vec<N>,
}
impl<N: Default> ImmutablePointToValues<N> {
pub fn ram_usage_bytes(&self) -> usize {
let Self {
point_entries,
values_container,
} = self;
point_entries.capacity() * std::mem::size_of::<PointValueEntry<N>>()
+ values_container.capacity() * std::mem::size_of::<N>()
}
pub fn new(src: Vec<Vec<N>>) -> Self {
let mut point_entries = Vec::with_capacity(src.len());
let container_capacity = src
.iter()
.map(|values| values.len())
.filter(|&size| size > 1)
.sum();
let mut values_container = Vec::with_capacity(container_capacity);
for values in src {
match values.len() {
0 => {
point_entries.push(PointValueEntry::default());
}
1 => {
let value = values.into_iter().next().expect("length checked above");
point_entries.push(PointValueEntry::Single(value));
}
2.. => {
let start = values_container.len() as u32;
let count = values.len() as u32;
point_entries.push(PointValueEntry::Slice { start, count });
values_container.extend(values);
}
}
}
Self {
point_entries,
values_container,
}
}
pub fn check_values_any(
&self,
idx: PointOffsetType,
mut check_fn: impl FnMut(&N) -> bool,
) -> bool {
let Some(entry) = self.point_entries.get(idx as usize) else {
return false;
};
match entry {
PointValueEntry::Single(v) => check_fn(v),
PointValueEntry::Slice { start, count } => {
let range = *start as usize..(*start + *count) as usize;
if let Some(values) = self.values_container.get(range) {
values.iter().any(check_fn)
} else {
false
}
}
}
}
pub fn get_values(&self, idx: PointOffsetType) -> Option<impl Iterator<Item = &N> + '_> {
Some(self.get_values_slice(idx)?.iter())
}
pub fn get_values_slice(&self, idx: PointOffsetType) -> Option<&[N]> {
let entry = self.point_entries.get(idx as usize)?;
match entry {
PointValueEntry::Single(v) => Some(std::slice::from_ref(v)),
PointValueEntry::Slice { start, count } => {
let range = *start as usize..(*start + *count) as usize;
Some(&self.values_container[range])
}
}
}
pub fn get_values_count(&self, idx: PointOffsetType) -> Option<usize> {
let entry = self.point_entries.get(idx as usize)?;
match entry {
PointValueEntry::Single(_) => Some(1),
PointValueEntry::Slice { start: _, count } => Some(*count as usize),
}
}
pub fn remove_point(&mut self, idx: PointOffsetType) -> Vec<N> {
if self.point_entries.len() <= idx as usize {
return Default::default();
}
let removed_entry = std::mem::take(&mut self.point_entries[idx as usize]);
match removed_entry {
PointValueEntry::Single(v) => vec![v],
PointValueEntry::Slice { start, count } => {
let mut result = Vec::with_capacity(count as usize);
for i in start..(start + count) {
let value = std::mem::take(&mut self.values_container[i as usize]);
result.push(value);
}
result
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check_values(point_to_values: &ImmutablePointToValues<i32>, values: &[Vec<i32>]) {
for (idx, expected) in values.iter().enumerate() {
let actual: Option<Vec<_>> = point_to_values
.get_values(idx as PointOffsetType)
.map(|i| i.copied().collect());
assert_eq!(actual, Some(expected.clone()));
}
}
#[test]
fn test_immutable_point_to_values_remove() {
let mut values = vec![
vec![0, 1, 2, 3, 4],
vec![5, 6, 7, 8, 9],
vec![0, 1, 2, 3, 4],
vec![5, 6, 7, 8, 9],
vec![10, 11, 12],
vec![],
vec![13],
vec![14, 15],
];
let mut point_to_values = ImmutablePointToValues::new(values.clone());
check_values(&point_to_values, values.as_slice());
point_to_values.remove_point(0);
values[0].clear();
check_values(&point_to_values, values.as_slice());
point_to_values.remove_point(3);
values[3].clear();
check_values(&point_to_values, values.as_slice());
}
#[test]
fn test_single_value_stored_inline() {
let src = vec![
vec![42], vec![1, 2], vec![99], vec![], ];
let ptv = ImmutablePointToValues::new(src);
assert_eq!(ptv.values_container, [1, 2]);
assert_eq!(
ptv.get_values(0).map(|i| i.copied().collect::<Vec<_>>()),
Some(vec![42]),
);
assert_eq!(
ptv.get_values(1).map(|i| i.copied().collect::<Vec<_>>()),
Some(vec![1, 2]),
);
assert_eq!(
ptv.get_values(2).map(|i| i.copied().collect::<Vec<_>>()),
Some(vec![99]),
);
assert_eq!(
ptv.get_values(3).map(|i| i.copied().collect::<Vec<_>>()),
Some(vec![]),
);
}
#[test]
fn test_get_values_count() {
let src = vec![vec![1, 2, 3], vec![10], vec![], vec![4, 5]];
let ptv = ImmutablePointToValues::new(src);
assert_eq!(ptv.get_values_count(0), Some(3));
assert_eq!(ptv.get_values_count(1), Some(1));
assert_eq!(ptv.get_values_count(2), Some(0));
assert_eq!(ptv.get_values_count(3), Some(2));
assert_eq!(ptv.get_values_count(100), None);
}
#[test]
fn test_check_values_any() {
let src = vec![vec![1, 2, 3], vec![10], vec![], vec![4, 5]];
let ptv = ImmutablePointToValues::new(src);
assert!(ptv.check_values_any(0, |v| *v == 2));
assert!(!ptv.check_values_any(0, |v| *v == 99));
assert!(ptv.check_values_any(1, |v| *v == 10));
assert!(!ptv.check_values_any(1, |v| *v == 0));
assert!(!ptv.check_values_any(2, |_| true));
assert!(!ptv.check_values_any(100, |_| true));
}
fn assert_point_entry_is_default(ptv: &ImmutablePointToValues<i32>, idx: PointOffsetType) {
assert_eq!(
ptv.get_values_count(idx),
Some(0),
"Expected values count to be Some(0) for removed point {idx}"
);
let vals: Vec<i32> = ptv
.get_values(idx)
.expect("Entry should still exist after removal")
.copied()
.collect();
assert!(
vals.is_empty(),
"Expected no values for removed point {idx}, got {vals:?}"
);
assert!(
!ptv.check_values_any(idx, |_| true),
"Expected check_values_any to return false for removed point {idx}"
);
}
#[test]
fn test_remove_single_value_point() {
let mut values = vec![
vec![10], vec![20, 30], vec![40], ];
let mut ptv = ImmutablePointToValues::new(values.clone());
let removed = ptv.remove_point(0);
assert_eq!(removed, vec![10]);
values[0].clear();
check_values(&ptv, &values);
assert_point_entry_is_default(&ptv, 0);
let removed = ptv.remove_point(2);
assert_eq!(removed, vec![40]);
values[2].clear();
check_values(&ptv, &values);
assert_point_entry_is_default(&ptv, 2);
let removed = ptv.remove_point(1);
assert_eq!(removed, vec![20, 30]);
values[1].clear();
check_values(&ptv, &values);
assert_point_entry_is_default(&ptv, 1);
}
#[test]
fn test_remove_out_of_bounds() {
let ptv_src = vec![vec![1]];
let mut ptv = ImmutablePointToValues::new(ptv_src);
let removed = ptv.remove_point(999);
assert!(removed.is_empty());
}
#[test]
fn test_get_values_out_of_bounds() {
let ptv = ImmutablePointToValues::<i32>::new(vec![vec![1]]);
assert!(ptv.get_values(10).is_none());
}
#[test]
fn test_all_single_values() {
let src = vec![vec![1], vec![2], vec![3], vec![4], vec![5]];
let ptv = ImmutablePointToValues::new(src);
assert!(ptv.values_container.is_empty());
for i in 0..5 {
assert_eq!(ptv.get_values_count(i), Some(1));
let vals: Vec<_> = ptv.get_values(i).unwrap().copied().collect();
assert_eq!(vals, vec![(i + 1) as i32]);
}
}
#[test]
fn test_empty_source() {
let mut ptv = ImmutablePointToValues::<i32>::new(vec![]);
assert!(ptv.get_values(0).is_none());
assert_eq!(ptv.get_values_count(0), None);
assert!(!ptv.check_values_any(0, |_| true));
assert!(ptv.remove_point(0).is_empty());
}
}