use crate::transducer::{AutomatonZipper, PathNode, StatePool};
use libdictenstein::zipper::DictZipper;
#[derive(Clone)]
pub struct IntersectionZipper<D>
where
D: DictZipper<Unit = u8>,
{
dict: D,
automaton: AutomatonZipper,
parent: Option<Box<PathNode<u8>>>,
}
impl<D> IntersectionZipper<D>
where
D: DictZipper<Unit = u8>,
{
pub fn new(dict: D, automaton: AutomatonZipper) -> Self {
IntersectionZipper {
dict,
automaton,
parent: None,
}
}
fn with_parent(dict: D, automaton: AutomatonZipper, parent: Option<Box<PathNode<u8>>>) -> Self {
IntersectionZipper {
dict,
automaton,
parent,
}
}
#[inline]
pub fn is_match(&self) -> bool {
if !self.dict.is_final() {
return false;
}
let term_length = self.depth();
self.automaton
.infer_distance(term_length)
.map(|dist| dist <= self.automaton.max_distance())
.unwrap_or(false)
}
#[inline]
pub fn distance(&self) -> Option<usize> {
if !self.dict.is_final() {
return None;
}
let term_length = self.depth();
let dist = self.automaton.infer_distance(term_length)?;
if dist <= self.automaton.max_distance() {
Some(dist)
} else {
None
}
}
#[inline]
pub fn depth(&self) -> usize {
self.parent.as_ref().map(|p| p.depth()).unwrap_or(0)
}
pub fn term(&self) -> String {
let mut units = Vec::new();
if let Some(parent) = &self.parent {
parent.collect_labels(&mut units);
}
units.reverse();
String::from_utf8_lossy(&units).into_owned()
}
pub fn children<'a>(
&'a self,
pool: &'a mut StatePool,
) -> impl Iterator<Item = (u8, Self)> + 'a {
let parent_for_children = self.parent.clone();
let automaton = self.automaton.clone();
self.dict.children().filter_map(move |(label, dict_child)| {
automaton.transition(label, pool).map(|auto_child| {
let new_parent = Some(Box::new(PathNode::new(label, parent_for_children.clone())));
let child = IntersectionZipper::with_parent(dict_child, auto_child, new_parent);
(label, child)
})
})
}
pub fn is_viable(&self) -> bool {
self.automaton.is_viable()
}
pub fn dict_zipper(&self) -> &D {
&self.dict
}
pub fn automaton_zipper(&self) -> &AutomatonZipper {
&self.automaton
}
}
#[cfg(all(test, feature = "pathmap-backend"))]
mod tests {
use super::*;
use crate::transducer::Algorithm;
use libdictenstein::pathmap::zipper::PathMapZipper;
use libdictenstein::pathmap::PathMapDictionary;
#[test]
fn test_new_creates_root() {
let dict = PathMapDictionary::<()>::new();
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"test", 1, Algorithm::Standard);
let intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
assert_eq!(intersection.depth(), 0);
assert!(!intersection.is_match());
assert!(intersection.is_viable());
}
#[test]
fn test_exact_match() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"cat", 0, Algorithm::Standard);
let mut intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
for expected in &[b'c', b'a', b't'] {
let children: Vec<_> = intersection.children(&mut pool).collect();
let mut found = false;
for (label, child) in children {
if &label == expected {
intersection = child;
found = true;
break;
}
}
assert!(found, "Should find edge for '{}'", *expected as char);
}
assert!(intersection.is_match());
assert_eq!(intersection.distance(), Some(0));
assert_eq!(intersection.term(), "cat");
assert_eq!(intersection.depth(), 3);
}
#[test]
fn test_fuzzy_match() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"bat", 1, Algorithm::Standard);
let mut intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
for expected in &[b'c', b'a', b't'] {
let children: Vec<_> = intersection.children(&mut pool).collect();
let mut found = false;
for (label, child) in children {
if &label == expected {
intersection = child;
found = true;
break;
}
}
assert!(found, "Should find edge for '{}'", *expected as char);
}
assert!(intersection.is_match());
assert_eq!(intersection.distance(), Some(1));
assert_eq!(intersection.term(), "cat");
}
#[test]
fn test_no_match_exceeds_distance() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"dog", 1, Algorithm::Standard);
let mut intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
for expected in &[b'c', b'a', b't'] {
let children: Vec<_> = intersection.children(&mut pool).collect();
let mut found = false;
for (label, child) in children {
if &label == expected {
intersection = child;
found = true;
break;
}
}
if !found {
return;
}
}
assert!(!intersection.is_match());
assert_eq!(intersection.distance(), None);
}
#[test]
fn test_children_iteration() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
dict.insert("car");
dict.insert("dog");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
let children: Vec<_> = intersection.children(&mut pool).collect();
assert_eq!(children.len(), 2);
let labels: Vec<u8> = children.iter().map(|(label, _)| *label).collect();
assert!(labels.contains(&b'c'));
assert!(labels.contains(&b'd'));
}
#[test]
fn test_multiple_matches() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
dict.insert("ca");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let mut intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
let children: Vec<_> = intersection.children(&mut pool).collect();
for (label, child) in children {
if label == b'c' {
intersection = child;
break;
}
}
let children: Vec<_> = intersection.children(&mut pool).collect();
for (label, child) in children {
if label == b'a' {
intersection = child;
break;
}
}
assert!(intersection.is_match());
assert_eq!(intersection.distance(), Some(0));
assert_eq!(intersection.term(), "ca");
let children: Vec<_> = intersection.children(&mut pool).collect();
for (label, child) in children {
if label == b't' {
intersection = child;
break;
}
}
assert!(intersection.is_match());
assert_eq!(intersection.distance(), Some(0));
assert_eq!(intersection.term(), "cat");
}
#[test]
fn test_term_reconstruction() {
let dict = PathMapDictionary::<()>::new();
dict.insert("hello");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"hello", 0, Algorithm::Standard);
let mut intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
for expected in b"hello" {
let children: Vec<_> = intersection.children(&mut pool).collect();
for (label, child) in children {
if label == *expected {
intersection = child;
break;
}
}
}
assert_eq!(intersection.term(), "hello");
assert_eq!(intersection.depth(), 5);
}
#[test]
fn test_empty_dictionary() {
let dict = PathMapDictionary::<()>::new();
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"test", 1, Algorithm::Standard);
let intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let mut pool = StatePool::new();
let children: Vec<_> = intersection.children(&mut pool).collect();
assert_eq!(children.len(), 0);
assert!(!intersection.is_match());
}
#[test]
fn test_clone_independence() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let auto_zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let intersection = IntersectionZipper::new(dict_zipper, auto_zipper);
let clone1 = intersection.clone();
let clone2 = intersection.clone();
let mut pool = StatePool::new();
let mut _z1 = clone1;
let children: Vec<_> = _z1.children(&mut pool).collect();
for (label, child) in children {
if label == b'c' {
_z1 = child;
break;
}
}
assert_eq!(clone2.depth(), 0);
}
}