use crate::zipper::{DictZipper, ValuedDictZipper};
pub trait ExcludingPrefixZipper: DictZipper {
fn iter_excluding<'a, P>(&self, excluded: &'a [P]) -> ExcludingIterator<'a, Self, P>
where
P: AsRef<[Self::Unit]>,
Self: Sized,
{
ExcludingIterator::new(self.clone(), excluded)
}
fn with_prefix_excluding<'a, P>(
&self,
prefix: &[Self::Unit],
excluded: &'a [P],
) -> Option<ExcludingIterator<'a, Self, P>>
where
P: AsRef<[Self::Unit]>,
Self: Sized,
{
let mut zipper = self.clone();
for &unit in prefix {
zipper = zipper.descend(unit)?;
}
Some(ExcludingIterator::new(zipper, excluded))
}
}
impl<Z: DictZipper> ExcludingPrefixZipper for Z {}
pub struct ExcludingIterator<'a, Z: DictZipper, P> {
stack: Vec<Z>,
excluded_prefixes: &'a [P],
}
impl<'a, Z: DictZipper, P: AsRef<[Z::Unit]>> ExcludingIterator<'a, Z, P> {
fn new(start_zipper: Z, excluded_prefixes: &'a [P]) -> Self {
let mut stack = Vec::with_capacity(16);
let start_path = start_zipper.path();
if !Self::starts_with_any_excluded_static(&start_path, excluded_prefixes) {
stack.push(start_zipper);
}
Self {
stack,
excluded_prefixes,
}
}
#[inline]
fn starts_with_any_excluded(&self, path: &[Z::Unit]) -> bool {
Self::starts_with_any_excluded_static(path, self.excluded_prefixes)
}
#[inline]
fn starts_with_any_excluded_static(path: &[Z::Unit], excluded_prefixes: &[P]) -> bool {
excluded_prefixes.iter().any(|excl| {
let excl = excl.as_ref();
path.len() >= excl.len() && path[..excl.len()] == *excl
})
}
}
impl<'a, Z: DictZipper, P: AsRef<[Z::Unit]>> Iterator for ExcludingIterator<'a, Z, P> {
type Item = (Vec<Z::Unit>, Z);
fn next(&mut self) -> Option<Self::Item> {
while let Some(zipper) = self.stack.pop() {
for (_label, child) in zipper.children() {
let child_path = child.path();
if !self.starts_with_any_excluded(&child_path) {
self.stack.push(child);
}
}
if zipper.is_final() {
return Some((zipper.path(), zipper));
}
}
None
}
}
pub trait ValuedExcludingPrefixZipper: ValuedDictZipper {
fn iter_values_excluding<'a, P>(
&self,
excluded: &'a [P],
) -> ValuedExcludingIterator<'a, Self, P>
where
P: AsRef<[Self::Unit]>,
Self: Sized,
{
ValuedExcludingIterator {
inner: ExcludingIterator::new(self.clone(), excluded),
}
}
fn with_prefix_values_excluding<'a, P>(
&self,
prefix: &[Self::Unit],
excluded: &'a [P],
) -> Option<ValuedExcludingIterator<'a, Self, P>>
where
P: AsRef<[Self::Unit]>,
Self: Sized,
{
let mut zipper = self.clone();
for &unit in prefix {
zipper = zipper.descend(unit)?;
}
Some(ValuedExcludingIterator {
inner: ExcludingIterator::new(zipper, excluded),
})
}
}
impl<Z: ValuedDictZipper> ValuedExcludingPrefixZipper for Z {}
pub struct ValuedExcludingIterator<'a, Z: ValuedDictZipper, P> {
inner: ExcludingIterator<'a, Z, P>,
}
impl<'a, Z: ValuedDictZipper, P: AsRef<[Z::Unit]>> Iterator for ValuedExcludingIterator<'a, Z, P> {
type Item = (Vec<Z::Unit>, Z::Value);
fn next(&mut self) -> Option<Self::Item> {
let (path, zipper) = self.inner.next()?;
let value = zipper.value()?;
Some((path, value))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::double_array_trie::DoubleArrayTrie;
use crate::double_array_trie_zipper::DoubleArrayTrieZipper;
#[test]
fn test_single_exclusion_null_prefix() {
let terms = vec!["\x00meta", "\x00index", "hello", "world"];
let dict = DoubleArrayTrie::from_terms(terms.iter());
let zipper = DoubleArrayTrieZipper::new_from_dict(&dict);
let excluded: &[&[u8]] = &[b"\x00"];
let mut results: Vec<String> = zipper
.iter_excluding(excluded)
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect();
results.sort();
assert_eq!(results, vec!["hello", "world"]);
}
#[test]
fn test_empty_exclusion_returns_all() {
let terms = vec!["cat", "dog", "fish"];
let dict = DoubleArrayTrie::from_terms(terms.iter());
let zipper = DoubleArrayTrieZipper::new_from_dict(&dict);
let excluded: &[&[u8]] = &[];
let mut results: Vec<String> = zipper
.iter_excluding(excluded)
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect();
results.sort();
assert_eq!(results, vec!["cat", "dog", "fish"]);
}
#[test]
fn test_multiple_exclusions() {
let terms = vec!["_internal", "_hidden", "public", "visible", "hidden_file"];
let dict = DoubleArrayTrie::from_terms(terms.iter());
let zipper = DoubleArrayTrieZipper::new_from_dict(&dict);
let excluded: &[&[u8]] = &[b"_", b"hid"];
let mut results: Vec<String> = zipper
.iter_excluding(excluded)
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect();
results.sort();
assert_eq!(results, vec!["public", "visible"]);
}
#[test]
fn test_with_prefix_excluding() {
let terms = vec!["api_v1", "api_v2", "api__internal", "web_v1"];
let dict = DoubleArrayTrie::from_terms(terms.iter());
let zipper = DoubleArrayTrieZipper::new_from_dict(&dict);
let excluded: &[&[u8]] = &[b"api__"];
let mut results: Vec<String> = zipper
.with_prefix_excluding(b"api_", excluded)
.unwrap()
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect();
results.sort();
assert_eq!(results, vec!["api_v1", "api_v2"]);
}
#[test]
fn test_valued_excluding_iterator() {
let terms_with_values = vec![("cat", 1usize), ("cats", 2), ("\x00meta", 99), ("dog", 3)];
let dict = DoubleArrayTrie::from_terms_with_values(terms_with_values.into_iter());
let zipper = DoubleArrayTrieZipper::new_from_dict(&dict);
let excluded: &[&[u8]] = &[b"\x00"];
let mut results: Vec<(String, usize)> = zipper
.iter_values_excluding(excluded)
.map(|(path, val)| (String::from_utf8(path).unwrap(), val))
.collect();
results.sort();
assert_eq!(
results,
vec![
("cat".to_string(), 1),
("cats".to_string(), 2),
("dog".to_string(), 3),
]
);
}
}