creature_feature 0.2.0

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

/// `Bookends<A,B>` is a featurizer combinator that will run 'A' on the beggining of the data and run 'B' on the end of the data.
/// Its main purpose is to make it easier to handle prefixes and suffices. Created by `bookends`
#[derive(Hash, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Debug)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct BookEnds<A, B> {
    front: A,
    back: B,
    front_size: usize,
    back_size: usize,
}

/// A tool to featurize prefixes and suffixes. `bookends((a, n), (b, m))` will run featurizer `a` for the first `n` tokens and will run featurizer `b` for the last `m` tokens. All tokens between are skipped. If the input is shorter than `n` (or `m`), the window is clamped to the available input (it does not panic).
/// # Example
/// ```
///use creature_feature::ftzrs::misc::FrontBack;
///use creature_feature::ftzrs::{bislice, bookends, trigram};
///use creature_feature::traits::Ftzr;
///
///
///let ftzr = bookends((bislice(), 4), (trigram(), 4));
///
///let feats: Vec<FrontBack<&str, String>> = ftzr.featurize("sesquipedalian");
///
///println!("{:?}", feats);
///
/// //>>> [Front("se"), Front("es"), Front("sq"), Back("lia"), Back("ian")]
/// ```
///
pub fn bookends<A, B>(front: (A, usize), back: (B, usize)) -> BookEnds<A, B> {
    BookEnds {
        front: front.0,
        front_size: front.1,
        back_size: back.1,
        back: back.0,
    }
}

// One blanket over `D: AsTokens` replaces the former base `&[T]` impl plus the
// `impl_ftrzs_2!`-generated `&Vec<T>` / `&[T; N]` / `&str` / `&String` forwards.
impl<'a, D, T: 'a, TA: 'a, TB: 'a, A, B> IterFtzr<&'a D> for BookEnds<A, B>
where
    D: AsTokens<Token = T> + ?Sized,
    A: IterFtzr<&'a [T], TokenGroup = TA>,
    B: IterFtzr<&'a [T], TokenGroup = TB>,
{
    type TokenGroup = FrontBack<TA, TB>;
    type Iter = BookEndsIter<A::Iter, B::Iter>;

    fn iterate_features(&self, origin: &'a D) -> Self::Iter {
        let origin = origin.as_tokens();
        // Clamp both windows to the input: short input degrades gracefully
        // (front/back see what's available) instead of panicking.
        let front_end = cmp::min(self.front_size, origin.len());
        let back_start = origin.len() - cmp::min(self.back_size, origin.len());
        BookEndsIter(
            true,
            self.front.iterate_features(&origin[..front_end]),
            self.back.iterate_features(&origin[back_start..]),
        )
    }
}

/// The associated Iterator for the [`IterFtzr`] implementation of [`BookEnds`]
#[derive(Hash, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Debug)]
pub struct BookEndsIter<A, B>(bool, A, B);

impl<A, B> Iterator for BookEndsIter<A, B>
where
    A: Iterator,
    B: Iterator,
{
    type Item = FrontBack<A::Item, B::Item>;
    fn next(&mut self) -> Option<Self::Item> {
        if self.0 {
            // it's still in the first section
            match self.1.next() {
                None => {
                    self.0 = false;
                    self.2.next().map(FrontBack::Back)
                }
                otherwise => otherwise.map(FrontBack::Front),
            }
        } else {
            // it's in the last section
            self.2.next().map(FrontBack::Back)
        }
    }
}

/// This is the TokenGroup for [`BookEnds`]. We need to differentiate between features at the front of a word and features at the back of a word. Otherwise suffixes and prefixes would be identical.
#[derive(Hash, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Debug)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum FrontBack<A, B> {
    /// the TokenGroup from running `A` on the front of the input
    Front(A),
    /// the TokenGroup from running `B` on the Back of the input
    Back(B),
}

impl<A, B, C> FeatureFrom<FrontBack<A, B>> for Merged<C>
where
    C: FeatureFrom<A> + FeatureFrom<B>,
{
    fn from(x: FrontBack<A, B>) -> Self {
        Merged(match x {
            FrontBack::Front(a) => FeatureFrom::from(a),
            FrontBack::Back(a) => FeatureFrom::from(a),
        })
    }
}

impl<A, B, Ax, Bx> FeatureFrom<FrontBack<A, B>> for Result<Ax, Bx>
where
    Ax: FeatureFrom<A>,
    Bx: FeatureFrom<B>,
{
    fn from(x: FrontBack<A, B>) -> Self {
        match x {
            FrontBack::Front(a) => Ok(FeatureFrom::from(a)),
            FrontBack::Back(a) => Err(FeatureFrom::from(a)),
        }
    }
}

impl<A, B, Ax, Bx> FeatureFrom<FrontBack<A, B>> for FrontBack<Ax, Bx>
where
    Ax: FeatureFrom<A>,
    Bx: FeatureFrom<B>,
{
    fn from(x: FrontBack<A, B>) -> Self {
        match x {
            FrontBack::Front(a) => FrontBack::Front(FeatureFrom::from(a)),
            FrontBack::Back(a) => FrontBack::Back(FeatureFrom::from(a)),
        }
    }
}

impl<Origin, A, B> Ftzr<Origin> for BookEnds<A, B>
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)
        }
    }
}