Skip to content

Commit

Permalink
feat: improve crate's API (#2)
Browse files Browse the repository at this point in the history
* fix: simplify API of `Tape` and `Band`

* feat: improve API using extension trait `Enroll`

Use extension trait called `Enroll` with functions to convert iterator
to a `Band` or `Tape`. Implement this extension trait for all types that
implement the `Iterator` trait.

* fix: correctly calculate len of `Band`

* chore: update crate version

* fix: document internal API of `Band`

* feat: implement `Iterator` trait for `Tape`

* test: add test for iterator implementation of `Tape`
  • Loading branch information
nfejzic committed Oct 7, 2023
1 parent ad005dd commit 38302d0
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 50 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ribbon"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["Nadir Fejzic <nadirfejzo@gmail.com>"]
description = "Tape machine for peeking through windows of iterators."
Expand Down
104 changes: 74 additions & 30 deletions src/band.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

use crate::{ribbon, Ribbon};

/// A fix-sized [`Ribbon`] backed up by an array of `N` elements. It cannot grow over the given fixed
/// length, and instead drops and/or returns items if no space is available at the given moment.
/// A fix-sized [`Ribbon`] backed up by an array of `N` elements. It cannot grow over the given
/// fixed length, and instead drops and/or returns items if no space is available at the given
/// moment.
///
/// [`Ribbon`]: crate::Ribbon
#[derive(Debug)]
pub struct Band<const LEN: usize, I, T> {
pub struct Band<const LEN: usize, I>
where
I: Iterator,
{
iter: I,
tape: [Option<T>; LEN],
tape: [Option<I::Item>; LEN],
head: usize,
len: usize,
}

impl<const LEN: usize, I, T> Band<LEN, I, T> {
impl<const LEN: usize, I> Band<LEN, I>
where
I: Iterator,
{
/// Creates a new `Tape` from the given iterator.
pub fn new(iter: I) -> Band<LEN, I, T>
where
I: Iterator<Item = T>,
T: Sized,
{
pub fn new(iter: I) -> Band<LEN, I> {
let tape = [0; LEN].map(|_| None);

Band {
Expand All @@ -32,39 +35,44 @@ impl<const LEN: usize, I, T> Band<LEN, I, T> {
}

/// Shifts all items by 1, returning the head of the `Band`.
fn slide(&mut self) -> Option<T> {
///
/// Shifting is a misnomer, and runs in `O(1)`. Rather than shifting elements, the indices
/// pointing to the first and last element are shifted.
fn slide(&mut self) -> Option<I::Item> {
let first = self.tape[self.head].take()?;

self.incr_head();
self.len = self.len.saturating_sub(1);

Some(first)
}

/// Checks if the `Band` is at full capacity.
fn is_full(&self) -> bool
where
I: Iterator<Item = T>,
{
fn is_full(&self) -> bool {
self.len() == LEN
}

/// Moves the head index by 1, wrapping around to the start of inner array when longer than
/// `LEN`.
fn incr_head(&mut self) {
self.head = (self.head + 1) % LEN;
}

/// Calculates the tail index based on head index and length of the `Band`.
fn tail(&self) -> usize {
(self.head + self.len.saturating_sub(1)) % LEN
}
}

impl<const LEN: usize, I, T> ribbon::Ribbon<T> for Band<LEN, I, T>
impl<const LEN: usize, I> ribbon::Ribbon<I::Item> for Band<LEN, I>
where
I: Iterator<Item = T>,
I: Iterator,
{
fn progress(&mut self) -> Option<T> {
fn progress(&mut self) -> Option<I::Item> {
let next = self.iter.next()?; // do nothing if iterator does not produce

let head = self.slide();
self.len += 1;

self.tape[self.tail()] = Some(next);
head
Expand All @@ -81,26 +89,26 @@ where
}
}

fn pop_front(&mut self) -> Option<T> {
fn pop_front(&mut self) -> Option<I::Item> {
self.slide()
}

fn peek_front(&self) -> Option<&T> {
fn peek_front(&self) -> Option<&I::Item> {
self.peek_at(0)
}

fn pop_back(&mut self) -> Option<T> {
fn pop_back(&mut self) -> Option<I::Item> {
let back = self.tape[self.tail()].take()?;
self.len -= 1;
Some(back)
}

fn peek_back(&self) -> Option<&T> {
fn peek_back(&self) -> Option<&I::Item> {
let idx = self.len().saturating_sub(1);
self.peek_at(idx)
}

fn peek_at(&self, index: usize) -> Option<&T> {
fn peek_at(&self, index: usize) -> Option<&I::Item> {
if index >= LEN {
return None;
}
Expand All @@ -114,14 +122,38 @@ where
}
}

impl<const LEN: usize, I> Iterator for Band<LEN, I>
where
I: Iterator,
{
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
if self.is_empty() {
self.expand_n(LEN);
}

self.pop_front()
}
}

impl<const LEN: usize, I> From<I> for Band<LEN, I>
where
I: Iterator,
{
fn from(value: I) -> Self {
Band::new(value)
}
}

#[cfg(test)]
mod tests {
use super::Band;
use crate::ribbon::Ribbon;
use crate::{ribbon::Ribbon, Enroll};

#[test]
fn expands() {
let mut band: Band<5, _, _> = Band::new(0u32..10u32);
let mut band: Band<5, _> = Band::new(0u32..10u32);

assert_eq!(band.peek_front(), None);
assert_eq!(band.peek_back(), None);
Expand All @@ -141,7 +173,7 @@ mod tests {

#[test]
fn pops_front() {
let mut band: Band<5, _, _> = Band::new(0u32..10u32);
let mut band: Band<5, _> = Band::new(0u32..10u32);
band.expand_n(5);

assert_eq!(band.pop_front(), Some(0));
Expand All @@ -154,7 +186,7 @@ mod tests {

#[test]
fn pops_back() {
let mut band: Band<5, _, _> = Band::new(0u32..10u32);
let mut band: Band<5, _> = Band::new(0u32..10u32);
dbg!(&band);
band.expand_n(5);
dbg!(&band);
Expand All @@ -170,7 +202,7 @@ mod tests {

#[test]
fn peeks_at() {
let mut band: Band<5, _, _> = Band::new(0u32..10u32);
let mut band: Band<5, _> = Band::new(0u32..10u32);
band.expand_n(5);

assert_eq!(band.peek_at(0), Some(&0));
Expand All @@ -183,7 +215,7 @@ mod tests {

#[test]
fn len_correct() {
let mut band: Band<5, _, _> = Band::new(0u32..10u32);
let mut band: Band<5, _> = Band::new(0u32..10u32);
band.expand_n(5);

assert_eq!(band.len(), 5);
Expand All @@ -206,7 +238,7 @@ mod tests {

#[test]
fn makes_progress() {
let mut band: Band<5, _, _> = Band::new(0u32..5u32);
let mut band: Band<5, _> = Band::new(0u32..5u32);

// band was empty, first progress has nothing to return
assert_eq!(band.progress(), None);
Expand All @@ -221,4 +253,16 @@ mod tests {
// iterator does not produce more values, so progress does not drop anything
assert_eq!(band.progress(), None);
}

#[test]
fn is_iterator() {
let mut band = (0..10).band::<5>();

assert_eq!(band.next(), Some(0));
assert_eq!(band.next(), Some(1));
assert_eq!(band.next(), Some(2));
assert_eq!(band.next(), Some(3));
assert_eq!(band.next(), Some(4));
assert_eq!(band.next(), Some(5));
}
}
9 changes: 4 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
//! ### Using `Tape`
//!
//! ```rust
//! use ribbon::{Ribbon, Tape};
//! use ribbon::{Ribbon, Tape, Enroll};
//!
//! let mut tape = Tape::new(0..10);
//! let mut tape = (0..10).tape();
//! tape.expand_n(5);
//!
//! assert_eq!(tape.len(), 5);
Expand All @@ -36,11 +36,10 @@
//! ### Using `Band`
//!
//! ```rust
//! use ribbon::Band;
//! use ribbon::Ribbon;
//! use ribbon::{Band, Ribbon, Enroll};
//!
//! // Band with capacity for 5 items
//! let mut band: Band<3, _, _> = Band::new(0..4);
//! let mut band = (0..4).band::<3>();
//! band.expand_n(2); // consume 0, 1 from iterator
//!
//! assert_eq!(band.len(), 2);
Expand Down
42 changes: 42 additions & 0 deletions src/ribbon.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::{Band, Tape};

pub trait Ribbon<T> {
/// Tries to stream the iterator forward through the `Ribbon` without expanding it. Underlying
/// iterator is polled for the next element. Returns the head of the `Ribbon`, and the new item
Expand Down Expand Up @@ -201,3 +203,43 @@ pub trait Ribbon<T> {
self.len() == 0
}
}

/// Extension trait on types that implement [`Iterator`] trait with convenient functions to convert
/// the given [`Iterator`] into a [`Band`] or [`Tape`].
///
/// [`Band`]: crate::Band
/// [`Tape`]: crate::Tape
pub trait Enroll {
/// Creates a new [`Band`] from the given Iterator.
///
/// [`Band`]: crate::Band
fn band<const N: usize>(self) -> crate::Band<N, Self>
where
Self: Sized + Iterator;

/// Creates a new [`Tape`] from the given Iterator.
///
/// [`Tape`]: crate::Tape
fn tape(self) -> crate::Tape<Self>
where
Self: Sized + Iterator;
}

impl<I> Enroll for I
where
I: Iterator,
{
fn band<const N: usize>(self) -> Band<N, Self>
where
Self: Sized + Iterator,
{
crate::Band::<N, Self>::new(self)
}

fn tape(self) -> Tape<Self>
where
Self: Sized + Iterator,
{
crate::Tape::new(self)
}
}

0 comments on commit 38302d0

Please sign in to comment.