use std::collections::BTreeSet;
use std::num::NonZeroU64;
#[derive(Debug, Default, Clone)]
pub enum AtomSelection {
#[default]
All,
Mask(Vec<bool>), Until(u32),
}
impl AtomSelection {
pub fn from_index_list(indices: &[u32]) -> Self {
let max = match indices.iter().max() {
Some(&max) => max as usize + 1,
None => return Self::Mask(Vec::new()),
};
let mut mask = Vec::with_capacity(max);
mask.resize(max, false);
for &idx in indices {
mask[idx as usize] = true;
}
Self::Mask(mask)
}
pub fn is_included(&self, idx: usize) -> Option<bool> {
let idx = idx as u32;
match self {
AtomSelection::All => Some(true),
AtomSelection::Mask(mask) => mask.get(idx as usize).copied(),
AtomSelection::Until(until) => {
if &idx <= until {
Some(true)
} else {
None
}
}
}
}
pub fn last(&self) -> Option<usize> {
match self {
AtomSelection::All => None,
AtomSelection::Mask(mask) => match mask.iter().rposition(|&entry| entry) {
Some(n) => Some(n + 1),
None => Some(0),
},
AtomSelection::Until(until) => Some(*until as usize),
}
}
}
#[derive(Debug, Default, Clone)]
pub enum FrameSelection {
#[default]
All,
Range(Range),
FrameList(BTreeSet<usize>),
}
impl FrameSelection {
pub fn framelist_from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = usize>,
{
Self::FrameList(BTreeSet::from_iter(iter))
}
pub fn is_included(&self, idx: usize) -> Option<bool> {
match self {
FrameSelection::All => Some(true),
FrameSelection::Range(range) => range.is_included(idx as u64),
FrameSelection::FrameList(indices) => {
if *indices.last()? < idx {
None
} else {
Some(indices.contains(&idx))
}
}
}
}
pub fn until(&self) -> Option<usize> {
match self {
FrameSelection::All => None,
FrameSelection::Range(range) => range.last().map(|last| last + 1),
FrameSelection::FrameList(list) => {
Some(list.iter().max().copied().unwrap_or_default() + 1)
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Range {
pub start: u64,
pub end: Option<u64>,
pub step: NonZeroU64,
}
impl Range {
pub fn new(start: Option<u64>, end: Option<u64>, step: Option<NonZeroU64>) -> Self {
let mut sel = Self {
end,
..Self::default()
};
if let Some(start) = start {
sel.start = start;
}
if let Some(step) = step {
sel.step = step;
}
if let Some(end) = sel.end {
let start = sel.start;
debug_assert!(
start <= end,
"the start of a selection ({start}) may not exceed the end ({end})"
);
}
sel
}
pub fn is_included(&self, idx: u64) -> Option<bool> {
if let Some(end) = self.end {
if end <= idx {
return None;
}
}
let in_range = self.start <= idx;
let in_step = self.step.get() == 1 || (idx + self.start) % self.step == 0;
Some(in_range && in_step)
}
pub fn last(&self) -> Option<usize> {
self.end.map(|end| {
let length = end.saturating_sub(self.start);
let remainder = length % self.step;
(end - remainder) as usize
})
}
}
impl Default for Range {
fn default() -> Self {
Self {
start: 0,
end: None,
step: NonZeroU64::new(1).unwrap(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod frame {
use std::num::NonZeroU64;
use super::{FrameSelection, Range};
#[test]
fn zero_selection() {
let list_empty = FrameSelection::FrameList(Default::default());
let list_zero = FrameSelection::framelist_from_iter([0]);
let range_empty = FrameSelection::Range(Range::new(None, Some(0), None));
for idx in 0..1000 {
assert!(list_empty.is_included(idx).is_none());
if idx > 0 {
assert!(list_zero.is_included(idx).is_none());
}
assert!(range_empty.is_included(idx).is_none());
}
}
#[test]
fn first_n() {
let n = 100;
let step = NonZeroU64::new(17).unwrap();
let list = FrameSelection::FrameList((0..n).collect());
let until = FrameSelection::Range(Range::new(None, Some(n as u64), None));
let from_n = FrameSelection::Range(Range::new(Some(n as u64), None, None));
let until_stepped = FrameSelection::Range(Range::new(None, Some(n as u64), Some(step)));
let from_n_stepped =
FrameSelection::Range(Range::new(Some(n as u64), None, Some(step)));
let all = FrameSelection::All;
for idx in 0..2 * n {
if idx < n {
assert_eq!(list.is_included(idx), Some(true));
assert_eq!(until.is_included(idx), Some(true));
assert_eq!(
until_stepped.is_included(idx),
Some(idx as u64 % step.get() == 0),
);
} else {
assert!(list.is_included(idx).is_none());
assert!(until.is_included(idx).is_none());
assert!(until_stepped.is_included(idx).is_none());
}
let from_n_included = idx >= n;
assert_eq!(from_n.is_included(idx), Some(from_n_included));
assert_eq!(
from_n_stepped.is_included(idx),
Some(from_n_included && (n as u64 + idx as u64) % step.get() == 0),
);
assert_eq!(all.is_included(idx), Some(true));
}
}
#[test]
fn until() {
let n = 100;
let step = NonZeroU64::new(17).unwrap();
let list = FrameSelection::FrameList((0..n).collect());
let until = FrameSelection::Range(Range::new(None, Some(n as u64), None));
let from_n = FrameSelection::Range(Range::new(Some(n as u64), None, None));
let until_stepped = FrameSelection::Range(Range::new(None, Some(n as u64), Some(step)));
let from_n_stepped =
FrameSelection::Range(Range::new(Some(n as u64), None, Some(step)));
let from_until_stepped =
FrameSelection::Range(Range::new(Some(n as u64 / 3), Some(n as u64), Some(step)));
let all = FrameSelection::All;
assert_eq!(list.until(), Some(n));
assert_eq!(until.until(), Some(n + 1));
assert!(from_n.until().is_none());
assert_eq!(until_stepped.until(), Some(86));
assert!(from_n_stepped.until().is_none());
assert_eq!(from_until_stepped.until(), Some(85));
assert!(all.until().is_none());
}
}
mod atom {
use super::AtomSelection;
#[test]
fn zero_selection() {
let m = 100;
let mask_empty = AtomSelection::Mask(vec![]);
let mask_false = AtomSelection::Mask(vec![false; m]);
let list_empty = AtomSelection::from_index_list(&[]);
let list_zero = AtomSelection::from_index_list(&[0]);
let until_zero = AtomSelection::Until(0);
for idx in 0..1000 {
assert!(mask_empty.is_included(idx).is_none());
if idx < m {
assert_eq!(mask_false.is_included(idx), Some(false));
} else {
assert!(mask_false.is_included(idx).is_none());
}
assert!(list_empty.is_included(idx).is_none());
if idx > 0 {
assert!(until_zero.is_included(idx).is_none());
assert!(list_zero.is_included(idx).is_none());
} else {
assert_eq!(until_zero.is_included(idx), Some(true));
assert_eq!(list_zero.is_included(idx), Some(true));
}
}
}
#[test]
fn first_n() {
let n = 100;
let mask = AtomSelection::Mask(vec![true; n]);
let mask_trailing_false = AtomSelection::Mask([vec![true; n], vec![false; n]].concat());
let list = AtomSelection::from_index_list(&(0..n as u32).collect::<Vec<_>>());
let until = AtomSelection::Until(n as u32 - 1);
let all = AtomSelection::All;
for idx in 0..2 * n {
if idx < n {
assert_eq!(mask.is_included(idx), Some(true));
assert_eq!(list.is_included(idx), Some(true));
assert_eq!(until.is_included(idx), Some(true));
} else {
assert!(mask.is_included(idx).is_none());
assert!(list.is_included(idx).is_none());
assert!(until.is_included(idx).is_none());
}
assert_eq!(mask_trailing_false.is_included(idx), Some(idx < n));
assert_eq!(all.is_included(idx), Some(true));
}
}
}
}