creature_feature 0.2.0

Composable n-gram combinators that are ergonomic and bare-metal fast.
Documentation
use crate::accum_ftzr::{Ftzr, IterFtzr, LinearFixed};
use crate::as_tokens::AsTokens;
#[cfg(feature = "serde1")]
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};

/// The type of a fixed-length n-gram over copied data. Created by `n_gram::<N>()`
#[derive(Hash, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Debug, Default)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct NGram<const N: usize>();

/// The associated iterator for `<NGram<N> as IterFtzr<T>>::Iter`
#[derive(Hash, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Debug)]
pub struct NGramIter<'a, T, const N: usize> {
    idx: usize,
    data: &'a [T],
}

impl<'a, T: 'a, const N: usize> Iterator for NGramIter<'a, T, N>
where
    [T; N]: TryFrom<&'a [T]>,
{
    type Item = [T; N];
    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        let jdx = self.idx + N;
        if jdx <= self.data.len() {
            let ret = Some(
                TryInto::try_into(&self.data[self.idx..jdx])
                    .map_err(|_| ())
                    .expect("Error converting from slice to [T;N]"),
            );
            self.idx += 1;
            ret
        } else {
            None
        }
    }
}

impl<const N: usize> LinearFixed for NGram<N> {
    fn chunk_size(&self) -> usize {
        N
    }
}

// One blanket over `D: AsTokens` replaces the former base `&[T]` impl and the
// four hand-written forwards (`&Vec<T>`, `&str`, `&String`, `&[T; M]`).
impl<'a, D, T: 'a, const N: usize> IterFtzr<&'a D> for NGram<N>
where
    D: AsTokens<Token = T> + ?Sized,
    [T; N]: TryFrom<&'a [T]>,
{
    type TokenGroup = [T; N];
    type Iter = NGramIter<'a, T, N>;

    fn iterate_features(&self, origin: &'a D) -> Self::Iter {
        NGramIter {
            idx: 0,
            data: origin.as_tokens(),
        }
    }
}

/// general n-grams over copied data, produces owned data (like String) or multiple `[T; N]`. (Compare to `n_slice`)
/// ```
/// use creature_feature::ftzrs::n_gram;
/// use creature_feature::traits::Ftzr;
///
/// let my_ftzr = n_gram::<7>();
///
/// let feats: Vec<[u8; 7]> = my_ftzr.featurize("0123456789");
/// let feats: Vec<String>  = my_ftzr.featurize("0123456789");
/// ```
pub fn n_gram<const N: usize>() -> NGram<N> {
    NGram::<N>()
}

/// bigrams over copied data, produces owned data (like String) or multiple `[T; 2]`. (Compare to `bislice`)
pub fn bigram() -> NGram<2> {
    NGram::<2>()
}

/// trigrams over copied data, produces owned data (like String) or multiple `[T; 3]`. (Compare to `trislice`)
pub fn trigram() -> NGram<3> {
    NGram::<3>()
}

impl<Origin, const N: usize> Ftzr<Origin> for NGram<N>
where
    Self: IterFtzr<Origin>,
{
    type TokenGroup = <Self as IterFtzr<Origin>>::TokenGroup;
    fn push_tokens<Push>(&self, origin: Origin, push: &mut Push)
    where
        Push: FnMut(Self::TokenGroup),
    {
        for t in self.iterate_features(origin) {
            push(t)
        }
    }
}