use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone)]
pub enum Small<T: Copy, const N: usize> {
Empty,
Inline {
buf: [T; N],
len: usize,
},
Spilled(Vec<T>),
}
impl<T: Copy, const N: usize> Small<T, N> {
#[must_use]
pub fn new() -> Small<T, N> {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
Small::Empty
}
pub fn push(&mut self, v: T) {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
match self {
Small::Empty => {
*self = Small::Inline {
buf: [v; N],
len: 1,
}
}
Small::Inline { buf, len } if *len < N => {
buf[*len] = v;
*len += 1;
}
Small::Inline { buf, len } => {
let mut spill = Vec::with_capacity(N * 2);
spill.extend_from_slice(&buf[..*len]);
spill.push(v);
*self = Small::Spilled(spill);
}
Small::Spilled(s) => s.push(v),
}
}
pub fn collect<I: IntoIterator<Item = T>>(it: I) -> Small<T, N> {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
let mut it = it.into_iter();
let Some(first) = it.next() else {
return Small::Empty;
};
let mut buf = [first; N];
let mut len = 1;
while let Some(v) = it.next() {
if len == N {
let mut spill = Vec::with_capacity(N * 2);
spill.extend_from_slice(&buf[..len]);
spill.push(v);
spill.extend(it);
return Small::Spilled(spill);
}
buf[len] = v;
len += 1;
}
Small::Inline { buf, len }
}
#[must_use]
pub fn as_slice(&self) -> &[T] {
match self {
Small::Empty => &[],
Small::Inline { buf, len } => &buf[..*len],
Small::Spilled(v) => v,
}
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
match self {
Small::Empty => &mut [],
Small::Inline { buf, len } => &mut buf[..*len],
Small::Spilled(v) => v,
}
}
#[must_use]
pub fn is_inline(&self) -> bool {
!matches!(self, Small::Spilled(_))
}
}
#[allow(clippy::derivable_impls)]
impl<T: Copy, const N: usize> Default for Small<T, N> {
fn default() -> Small<T, N> {
Small::Empty
}
}
impl<T: Copy, const N: usize> Deref for Small<T, N> {
type Target = [T];
fn deref(&self) -> &[T] {
self.as_slice()
}
}
impl<T: Copy, const N: usize> DerefMut for Small<T, N> {
fn deref_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<'a, T: Copy, const N: usize> IntoIterator for &'a Small<T, N> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> std::slice::Iter<'a, T> {
self.as_slice().iter()
}
}
impl<'a, T: Copy, const N: usize> IntoIterator for &'a mut Small<T, N> {
type Item = &'a mut T;
type IntoIter = std::slice::IterMut<'a, T>;
fn into_iter(self) -> std::slice::IterMut<'a, T> {
self.as_mut_slice().iter_mut()
}
}
impl<T: Copy, const N: usize> FromIterator<T> for Small<T, N> {
fn from_iter<I: IntoIterator<Item = T>>(it: I) -> Small<T, N> {
Small::collect(it)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_one_is_an_empty_slice() {
let s: Small<u32, 4> = Small::new();
assert!(s.is_empty());
assert_eq!(&*s, &[] as &[u32]);
assert!(s.is_inline());
}
#[test]
fn everything_up_to_n_stays_on_the_stack() {
for n in 1..=4usize {
let s: Small<u32, 4> = Small::collect(0..n as u32);
assert!(s.is_inline(), "{n} elements spilled and should not have");
assert_eq!(&*s, &(0..n as u32).collect::<Vec<_>>()[..]);
}
}
#[test]
fn one_past_n_spills_and_keeps_everything() {
let s: Small<u32, 4> = Small::collect(0..5);
assert!(!s.is_inline(), "five in a four did not spill");
assert_eq!(&*s, &[0, 1, 2, 3, 4]);
}
#[test]
fn a_long_spill_keeps_the_order() {
let s: Small<u32, 4> = Small::collect(0..1_000);
assert!(!s.is_inline());
assert_eq!(s.len(), 1_000);
assert!(s.iter().copied().eq(0..1_000));
}
#[test]
fn it_can_be_sorted_in_place_either_way_round() {
let mut small: Small<u32, 4> = Small::collect([3, 1, 2]);
small.sort_unstable();
assert_eq!(&*small, &[1, 2, 3]);
let mut big: Small<u32, 4> = Small::collect([9, 3, 1, 2, 7, 5]);
big.sort_unstable();
assert_eq!(&*big, &[1, 2, 3, 5, 7, 9]);
}
#[test]
fn it_holds_references() {
let owned = [1u32, 2, 3];
let s: Small<&u32, 4> = owned.iter().collect();
assert_eq!(s.iter().copied().copied().collect::<Vec<_>>(), [1, 2, 3]);
}
#[test]
fn the_padding_is_not_part_of_the_slice() {
let s: Small<u32, 8> = Small::collect([7, 8]);
assert_eq!(&*s, &[7, 8]);
assert_eq!(s.len(), 2);
}
}