pub mod varint;
#[cfg(feature = "sbits")]
pub mod ef;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum Error {
#[error("ids are not strictly increasing at index {index}: prev={prev}, next={next}")]
NotStrictlyIncreasing {
index: usize,
prev: u32,
next: u32,
},
#[error("u32 overflow while decoding at index {index}")]
Overflow {
index: usize,
},
#[error("id at index {index} is outside the universe: id={id}, universe_size={universe_size}")]
IdOutOfUniverse {
index: usize,
id: u32,
universe_size: u32,
},
}
pub fn gaps_from_sorted_ids(ids: &[u32]) -> Result<Vec<u32>, Error> {
if ids.is_empty() {
return Ok(Vec::new());
}
let mut out = Vec::with_capacity(ids.len());
out.push(ids[0]);
for i in 1..ids.len() {
let prev = ids[i - 1];
let next = ids[i];
if next <= prev {
return Err(Error::NotStrictlyIncreasing {
index: i,
prev,
next,
});
}
out.push(next - prev);
}
Ok(out)
}
pub fn gaps_from_sorted_ids_unchecked(ids: &[u32]) -> Vec<u32> {
let mut out = Vec::with_capacity(ids.len());
let mut prev = 0u32;
for (i, &id) in ids.iter().enumerate() {
if i == 0 {
out.push(id);
prev = id;
} else {
out.push(id.saturating_sub(prev));
prev = id;
}
}
out
}
pub fn ids_from_gaps(gaps: &[u32]) -> Result<Vec<u32>, Error> {
let mut out = Vec::with_capacity(gaps.len());
let mut cur = 0u32;
for (i, &g) in gaps.iter().enumerate() {
if i == 0 {
cur = g;
} else {
cur = cur.checked_add(g).ok_or(Error::Overflow { index: i })?;
}
out.push(cur);
}
Ok(out)
}
pub fn ids_from_gaps_unchecked(gaps: &[u32]) -> Vec<u32> {
let mut out = Vec::with_capacity(gaps.len());
let mut cur = 0u32;
for (i, &g) in gaps.iter().enumerate() {
if i == 0 {
cur = g;
} else {
cur = cur.saturating_add(g);
}
out.push(cur);
}
out
}
pub fn validate_sorted_ids(ids: &[u32], universe_size: u32) -> Result<(), Error> {
for (index, &id) in ids.iter().enumerate() {
if id >= universe_size {
return Err(Error::IdOutOfUniverse {
index,
id,
universe_size,
});
}
if let Some(&prev) = index.checked_sub(1).and_then(|prev| ids.get(prev)) {
if id <= prev {
return Err(Error::NotStrictlyIncreasing {
index,
prev,
next: id,
});
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn gaps_rejects_unsorted() {
let err = gaps_from_sorted_ids(&[10, 10]).unwrap_err();
assert_eq!(
err,
Error::NotStrictlyIncreasing {
index: 1,
prev: 10,
next: 10
}
);
}
#[test]
fn ids_from_gaps_rejects_overflow() {
let err = ids_from_gaps(&[u32::MAX, 1]).unwrap_err();
assert_eq!(err, Error::Overflow { index: 1 });
}
#[test]
fn validate_sorted_ids_rejects_duplicates() {
let err = validate_sorted_ids(&[1, 3, 3], 10).unwrap_err();
assert_eq!(
err,
Error::NotStrictlyIncreasing {
index: 2,
prev: 3,
next: 3
}
);
}
#[test]
fn validate_sorted_ids_rejects_ids_outside_universe() {
let err = validate_sorted_ids(&[1, 10], 10).unwrap_err();
assert_eq!(
err,
Error::IdOutOfUniverse {
index: 1,
id: 10,
universe_size: 10
}
);
}
proptest! {
#[test]
fn gaps_roundtrip_strictly_increasing(mut ids in prop::collection::vec(0u32..1_000_000u32, 0..200)) {
ids.sort_unstable();
ids.dedup();
let gaps = gaps_from_sorted_ids(&ids).unwrap();
let back = ids_from_gaps(&gaps).unwrap();
prop_assert_eq!(back, ids);
}
}
}