use std::collections::VecDeque;
use super::{EndState, Staged, StagedToken, Token};
const MINIMUM_SHINGLE_SIZE: usize = 2;
const FILLER_TOKEN: &str = "_";
const TOKEN_SEPARATOR: &str = " ";
const SHINGLE_TYPE: &str = "shingle";
pub(super) fn filter(stream: Staged, maximum_shingle_size: usize) -> Staged {
let Staged { tokens, end } = stream;
let mut shingler = Shingler::over(&tokens, end, maximum_shingle_size);
let mut produced = Vec::new();
while let Some(token) = shingler.next_token() {
let stop = shingler.last_stop;
produced.push(StagedToken { token, stop });
}
Staged {
tokens: produced,
end,
}
}
#[derive(Clone)]
struct WindowToken {
term: String,
start_offset: u32,
end_offset: u32,
token_type: String,
is_filler: bool,
}
struct Shingler<'stream> {
input: &'stream [StagedToken],
input_end: EndState,
at: usize,
exhausted: bool,
fillers_to_insert: usize,
held_token: Option<WindowToken>,
deliver_held: bool,
window: VecDeque<WindowToken>,
maximum_shingle_size: usize,
gram_size: usize,
previous_gram_size: usize,
is_output_here: bool,
last_stop: EndState,
}
impl<'stream> Shingler<'stream> {
fn over(
input: &'stream [StagedToken],
input_end: EndState,
maximum_shingle_size: usize,
) -> Self {
Self {
input,
input_end,
at: 0,
exhausted: false,
fillers_to_insert: 0,
held_token: None,
deliver_held: false,
window: VecDeque::new(),
maximum_shingle_size,
gram_size: 1,
previous_gram_size: 1,
is_output_here: false,
last_stop: input_end,
}
}
fn next_token(&mut self) -> Option<Token> {
let mut gram = String::new();
let mut built = 0usize;
if self.gram_size == 1 || self.window.len() < self.gram_size {
self.shift_window();
} else {
built = self.previous_gram_size;
for at in 0..built {
if at > 0 {
gram.push_str(TOKEN_SEPARATOR);
}
gram.push_str(&self.window[at].term);
}
}
if self.window.len() < self.gram_size {
return None;
}
let mut is_all_filler = true;
let mut last_end_offset = 0;
let mut at = 0usize;
while at < self.window.len() && built < self.gram_size {
let gram_number = at + 1;
let token = self.window[at].clone();
last_end_offset = token.end_offset;
if built < gram_number {
if built > 0 {
gram.push_str(TOKEN_SEPARATOR);
}
gram.push_str(&token.term);
built += 1;
}
if is_all_filler && token.is_filler {
if gram_number == self.gram_size {
self.advance();
}
} else {
is_all_filler = false;
}
at += 1;
}
if is_all_filler || built != self.gram_size {
return None;
}
let head = self.window.front()?;
let token = Token {
term: gram,
position_increment: u32::from(!self.is_output_here),
start_offset: head.start_offset,
end_offset: last_end_offset,
token_type: if self.gram_size > 1 {
SHINGLE_TYPE.to_owned()
} else {
head.token_type.clone()
},
};
self.is_output_here = true;
self.advance();
Some(token)
}
fn advance(&mut self) {
self.previous_gram_size = self.gram_size;
if self.gram_size == 1 {
self.gram_size = MINIMUM_SHINGLE_SIZE;
} else if self.gram_size == self.maximum_shingle_size {
self.reset_gram_size();
} else {
self.gram_size += 1;
}
}
fn reset_gram_size(&mut self) {
self.gram_size = 1;
self.previous_gram_size = 1;
}
fn shift_window(&mut self) {
self.window.pop_front();
while self.window.len() < self.maximum_shingle_size {
match self.next_window_token() {
Some(token) => self.window.push_back(token),
None => break,
}
}
self.reset_gram_size();
self.is_output_here = false;
}
fn next_window_token(&mut self) -> Option<WindowToken> {
if self.fillers_to_insert > 0 {
self.fillers_to_insert -= 1;
let mut filler = self.held_token.clone()?;
filler.end_offset = filler.start_offset;
FILLER_TOKEN.clone_into(&mut filler.term);
filler.is_filler = true;
return Some(filler);
}
if self.deliver_held {
self.deliver_held = false;
return self.held_token.take();
}
if self.exhausted {
return None;
}
let Some(staged) = self.input.get(self.at) else {
self.exhausted = true;
self.last_stop = self.input_end;
self.fillers_to_insert =
(self.input_end.position_increment as usize).min(self.maximum_shingle_size - 1);
if self.fillers_to_insert == 0 {
return None;
}
self.deliver_held = false;
self.held_token = Some(WindowToken {
term: String::new(),
start_offset: self.input_end.offset,
end_offset: self.input_end.offset,
token_type: String::new(),
is_filler: false,
});
return self.next_window_token();
};
self.at += 1;
self.last_stop = staged.stop;
let token = &staged.token;
let window_token = WindowToken {
term: token.term.clone(),
start_offset: token.start_offset,
end_offset: token.end_offset,
token_type: token.token_type.clone(),
is_filler: false,
};
if token.position_increment > 1 {
self.fillers_to_insert =
((token.position_increment - 1) as usize).min(self.maximum_shingle_size - 1) - 1;
self.held_token = Some(window_token.clone());
self.deliver_held = true;
return Some(WindowToken {
term: FILLER_TOKEN.to_owned(),
end_offset: window_token.start_offset,
is_filler: true,
..window_token
});
}
Some(window_token)
}
}