use aho_corasick::{AhoCorasick, AhoCorasickBuilder, MatchKind};
use rustc_hash::FxHashMap;
pub struct AddedTokens {
matcher: AhoCorasick,
ids: Vec<u32>,
}
impl AddedTokens {
pub fn new(map: &FxHashMap<String, u32>) -> Option<Self> {
if map.is_empty() {
return None;
}
let entries: Vec<(&String, u32)> = map.iter().map(|(k, v)| (k, *v)).collect();
let patterns: Vec<&str> = entries.iter().map(|(k, _)| k.as_str()).collect();
let ids: Vec<u32> = entries.iter().map(|(_, v)| *v).collect();
let matcher = AhoCorasickBuilder::new()
.match_kind(MatchKind::LeftmostLongest)
.build(&patterns)
.ok()?;
Some(Self { matcher, ids })
}
pub fn encode_with<F>(&self, text: &str, mut encode_gap: F) -> Vec<u32>
where
F: FnMut(&str) -> Vec<u32>,
{
let mut out = Vec::new();
let mut last = 0;
for m in self.matcher.find_iter(text) {
if m.start() > last {
out.extend(encode_gap(&text[last..m.start()]));
}
out.push(self.ids[m.pattern().as_usize()]);
last = m.end();
}
if last < text.len() {
out.extend(encode_gap(&text[last..]));
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefers_longest_overlapping_added_token() {
let mut map = FxHashMap::default();
map.insert(" ".to_string(), 10);
map.insert(" ".to_string(), 20);
let at = AddedTokens::new(&map).unwrap();
let ids = at.encode_with("a b", |gap| gap.bytes().map(u32::from).collect());
assert_eq!(ids, vec![u32::from(b'a'), 20, u32::from(b'b')]);
}
}