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,
},
}
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
}
#[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 });
}
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);
}
}
}