pub mod lattice;
pub mod merge_strategies;
use std::collections::HashSet;
use crate::zipper::{DictZipper, ValuedDictZipper};
pub use lattice::{Lattice, LatticeJoin, LatticeMeet};
pub use merge_strategies::{FirstWins, LastWins, ValueMergeStrategy};
#[derive(Clone, Debug)]
pub struct UnionZipper<Z: DictZipper, S = FirstWins> {
zippers: Vec<Option<Z>>,
path: Vec<Z::Unit>,
strategy: S,
}
impl<Z: DictZipper> UnionZipper<Z, FirstWins> {
pub fn new(zippers: Vec<Z>) -> Self {
Self {
zippers: zippers.into_iter().map(Some).collect(),
path: Vec::new(),
strategy: FirstWins,
}
}
}
impl<Z: DictZipper, S: Clone + Send + Sync> UnionZipper<Z, S> {
pub fn with_strategy(zippers: Vec<Z>, strategy: S) -> Self {
Self {
zippers: zippers.into_iter().map(Some).collect(),
path: Vec::new(),
strategy,
}
}
pub fn dictionary_count(&self) -> usize {
self.zippers.len()
}
pub fn active_dictionary_count(&self) -> usize {
self.zippers.iter().filter(|z| z.is_some()).count()
}
pub fn iter(&self) -> UnionIterator<Z, S> {
UnionIterator::new(self.clone())
}
}
impl<Z: DictZipper, S: Clone + Send + Sync> DictZipper for UnionZipper<Z, S> {
type Unit = Z::Unit;
fn is_final(&self) -> bool {
self.zippers
.iter()
.any(|z| z.as_ref().is_some_and(|z| z.is_final()))
}
fn descend(&self, label: Self::Unit) -> Option<Self> {
let new_zippers: Vec<Option<Z>> = self
.zippers
.iter()
.map(|z| z.as_ref().and_then(|z| z.descend(label)))
.collect();
if new_zippers.iter().any(|z| z.is_some()) {
let mut new_path = self.path.clone();
new_path.push(label);
Some(Self {
zippers: new_zippers,
path: new_path,
strategy: self.strategy.clone(),
})
} else {
None
}
}
fn children(&self) -> impl Iterator<Item = (Self::Unit, Self)> {
let mut labels: Vec<Z::Unit> = self
.zippers
.iter()
.filter_map(|z| z.as_ref())
.flat_map(|z| z.children().map(|(label, _)| label))
.collect();
labels.sort_by(|a, b| {
format!("{:?}", a).cmp(&format!("{:?}", b))
});
labels.dedup();
let self_clone = self.clone();
labels
.into_iter()
.filter_map(move |label| self_clone.descend(label).map(|child| (label, child)))
}
fn path(&self) -> Vec<Self::Unit> {
self.path.clone()
}
}
impl<Z: ValuedDictZipper, S: ValueMergeStrategy<Z::Value> + Clone + Send + Sync> ValuedDictZipper
for UnionZipper<Z, S>
{
type Value = Z::Value;
fn value(&self) -> Option<Self::Value> {
let mut result: Option<Z::Value> = None;
for zipper in self.zippers.iter().filter_map(|z| z.as_ref()) {
if let Some(v) = zipper.value() {
result = Some(match result {
Some(existing) => self.strategy.merge(existing, v),
None => v,
});
}
}
result
}
}
pub struct UnionIterator<Z: DictZipper, S = FirstWins> {
stack: Vec<UnionZipper<Z, S>>,
seen: HashSet<Vec<Z::Unit>>,
}
impl<Z: DictZipper, S: Clone + Send + Sync> UnionIterator<Z, S> {
fn new(zipper: UnionZipper<Z, S>) -> Self {
let mut stack = Vec::with_capacity(16);
stack.push(zipper);
Self {
stack,
seen: HashSet::new(),
}
}
}
impl<Z: DictZipper, S: Clone + Send + Sync> Iterator for UnionIterator<Z, S> {
type Item = (Vec<Z::Unit>, UnionZipper<Z, S>);
fn next(&mut self) -> Option<Self::Item> {
while let Some(zipper) = self.stack.pop() {
for (_label, child) in zipper.children() {
self.stack.push(child);
}
if zipper.is_final() {
let path = zipper.path();
if self.seen.insert(path.clone()) {
return Some((path, zipper));
}
}
}
None
}
}
pub struct ValuedUnionIterator<Z: ValuedDictZipper, S> {
inner: UnionIterator<Z, S>,
}
impl<Z: ValuedDictZipper, S: ValueMergeStrategy<Z::Value> + Clone + Send + Sync>
ValuedUnionIterator<Z, S>
{
pub fn new(zipper: UnionZipper<Z, S>) -> Self {
Self {
inner: UnionIterator::new(zipper),
}
}
}
impl<Z: ValuedDictZipper, S: ValueMergeStrategy<Z::Value> + Clone + Send + Sync> Iterator
for ValuedUnionIterator<Z, S>
{
type Item = (Vec<Z::Unit>, Z::Value);
fn next(&mut self) -> Option<Self::Item> {
loop {
let (path, zipper) = self.inner.next()?;
if let Some(value) = zipper.value() {
return Some((path, value));
}
}
}
}
pub trait UnionZipperExt: DictZipper + Sized {
fn union_with(self, other: Self) -> UnionZipper<Self> {
UnionZipper::new(vec![self, other])
}
fn union_all(self, others: impl IntoIterator<Item = Self>) -> UnionZipper<Self> {
let mut zippers = vec![self];
zippers.extend(others);
UnionZipper::new(zippers)
}
}
impl<Z: DictZipper> UnionZipperExt for Z {}
pub trait ValuedUnionZipperExt: ValuedDictZipper + Sized {
fn union_with_strategy<S: ValueMergeStrategy<Self::Value> + Clone + Send + Sync>(
self,
other: Self,
strategy: S,
) -> UnionZipper<Self, S> {
UnionZipper::with_strategy(vec![self, other], strategy)
}
}
impl<Z: ValuedDictZipper> ValuedUnionZipperExt for Z {}
#[cfg(test)]
mod tests {
use super::*;
use crate::double_array_trie::DoubleArrayTrie;
use crate::double_array_trie_zipper::DoubleArrayTrieZipper;
fn sorted_strings(mut v: Vec<String>) -> Vec<String> {
v.sort();
v
}
#[test]
fn test_union_basic() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat", "dog"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["fish", "bird"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::new(vec![z1, z2]);
let results: Vec<String> = sorted_strings(
union
.iter()
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect(),
);
assert_eq!(results, vec!["bird", "cat", "dog", "fish"]);
}
#[test]
fn test_union_with_overlap() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat", "dog"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["cat", "fish"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = z1.union_with(z2);
let results: Vec<String> = sorted_strings(
union
.iter()
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect(),
);
assert_eq!(results, vec!["cat", "dog", "fish"]);
}
#[test]
fn test_union_descend() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat", "car"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["cab", "can"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = z1.union_with(z2);
let ca = union
.descend(b'c')
.and_then(|z| z.descend(b'a'))
.expect("Should be able to descend to 'ca'");
let mut children: Vec<u8> = ca.children().map(|(label, _)| label).collect();
children.sort();
assert_eq!(children, vec![b'b', b'n', b'r', b't']);
}
#[test]
fn test_union_is_final() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["dog"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = z1.union_with(z2);
let cat = union
.descend(b'c')
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b't'))
.expect("Should find 'cat'");
assert!(cat.is_final());
assert_eq!(cat.path(), b"cat".to_vec());
}
#[test]
fn test_union_nonexistent_path() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["dog"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = z1.union_with(z2);
assert!(union.descend(b'x').is_none());
}
#[test]
fn test_union_empty_dictionaries() {
let dict1: DoubleArrayTrie = DoubleArrayTrie::new();
let dict2: DoubleArrayTrie = DoubleArrayTrie::new();
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = z1.union_with(z2);
let count = union.iter().count();
assert_eq!(count, 0);
}
#[test]
fn test_union_one_empty() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat", "dog"].iter());
let dict2: DoubleArrayTrie = DoubleArrayTrie::new();
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = z1.union_with(z2);
let results: Vec<String> = sorted_strings(
union
.iter()
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect(),
);
assert_eq!(results, vec!["cat", "dog"]);
}
#[test]
fn test_valued_union_first_wins() {
let dict1 =
DoubleArrayTrie::from_terms_with_values(vec![("cat", 1usize), ("dog", 2)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(
vec![("cat", 10usize), ("fish", 3)].into_iter(),
);
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::new(vec![z1, z2]);
let cat = union
.descend(b'c')
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b't'))
.expect("Should find 'cat'");
assert_eq!(cat.value(), Some(1));
}
#[test]
fn test_valued_union_last_wins() {
let dict1 =
DoubleArrayTrie::from_terms_with_values(vec![("cat", 1usize), ("dog", 2)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(
vec![("cat", 10usize), ("fish", 3)].into_iter(),
);
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LastWins);
let cat = union
.descend(b'c')
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b't'))
.expect("Should find 'cat'");
assert_eq!(cat.value(), Some(10));
}
#[test]
fn test_valued_union_iterator() {
let dict1 =
DoubleArrayTrie::from_terms_with_values(vec![("cat", 1usize), ("dog", 2)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(
vec![("cat", 10usize), ("fish", 3)].into_iter(),
);
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::new(vec![z1, z2]);
let valued_iter = ValuedUnionIterator::new(union);
let mut results: Vec<(String, usize)> = valued_iter
.map(|(path, val)| (String::from_utf8(path).unwrap(), val))
.collect();
results.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(
results,
vec![
("cat".to_string(), 1), ("dog".to_string(), 2),
("fish".to_string(), 3),
]
);
}
#[test]
fn test_union_all() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["dog"].iter());
let dict3 = DoubleArrayTrie::from_terms(vec!["fish"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let z3 = DoubleArrayTrieZipper::new_from_dict(&dict3);
let union = z1.union_all(vec![z2, z3]);
let results: Vec<String> = sorted_strings(
union
.iter()
.map(|(path, _)| String::from_utf8(path).unwrap())
.collect(),
);
assert_eq!(results, vec!["cat", "dog", "fish"]);
}
#[test]
fn test_dictionary_count() {
let dict1 = DoubleArrayTrie::from_terms(vec!["cat"].iter());
let dict2 = DoubleArrayTrie::from_terms(vec!["dog"].iter());
let dict3 = DoubleArrayTrie::from_terms(vec!["fish"].iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let z3 = DoubleArrayTrieZipper::new_from_dict(&dict3);
let union = z1.union_all(vec![z2, z3]);
assert_eq!(union.dictionary_count(), 3);
assert_eq!(union.active_dictionary_count(), 3);
let c = union.descend(b'c').unwrap();
assert_eq!(c.dictionary_count(), 3);
assert_eq!(c.active_dictionary_count(), 1);
}
#[test]
fn test_custom_merge_strategy() {
#[derive(Clone)]
struct Sum;
impl ValueMergeStrategy<usize> for Sum {
fn merge(&self, existing: usize, new: usize) -> usize {
existing + new
}
}
let dict1 = DoubleArrayTrie::from_terms_with_values(vec![("cat", 1usize)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(vec![("cat", 10usize)].into_iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], Sum);
let cat = union
.descend(b'c')
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b't'))
.expect("Should find 'cat'");
assert_eq!(cat.value(), Some(11));
}
#[test]
fn test_lattice_numeric_u32() {
assert_eq!(5u32.join(&3), 5);
assert_eq!(3u32.join(&5), 5);
assert_eq!(5u32.meet(&3), 3);
assert_eq!(3u32.meet(&5), 3);
assert_eq!(5u32.join(&5), 5);
assert_eq!(5u32.meet(&5), 5);
}
#[test]
fn test_lattice_numeric_i32() {
assert_eq!((-5i32).join(&3), 3);
assert_eq!((-5i32).meet(&3), -5);
}
#[test]
fn test_lattice_numeric_f64() {
assert_eq!(5.0f64.join(&3.0), 5.0);
assert_eq!(5.0f64.meet(&3.0), 3.0);
}
#[test]
fn test_lattice_bool() {
assert!(true.join(&false));
assert!(false.join(&true));
assert!(true.join(&true));
assert!(!false.join(&false));
assert!(true.meet(&true));
assert!(!true.meet(&false));
assert!(!false.meet(&true));
assert!(!false.meet(&false));
}
#[test]
fn test_lattice_option() {
let some_5 = Some(5u32);
let some_3 = Some(3u32);
let none: Option<u32> = None;
assert_eq!(some_5.join(&some_3), Some(5)); assert_eq!(some_5.join(&none), Some(5));
assert_eq!(none.join(&some_3), Some(3));
assert_eq!(none.join(&none), None);
assert_eq!(some_5.meet(&some_3), Some(3)); assert_eq!(some_5.meet(&none), None);
assert_eq!(none.meet(&some_3), None);
assert_eq!(none.meet(&none), None);
}
#[test]
fn test_lattice_hashset() {
let set1: HashSet<i32> = [1, 2, 3].into_iter().collect();
let set2: HashSet<i32> = [2, 3, 4].into_iter().collect();
let joined = set1.join(&set2);
assert_eq!(joined, [1, 2, 3, 4].into_iter().collect());
let met = set1.meet(&set2);
assert_eq!(met, [2, 3].into_iter().collect());
}
#[test]
fn test_lattice_hashset_disjoint() {
let set1: HashSet<i32> = [1, 2].into_iter().collect();
let set2: HashSet<i32> = [3, 4].into_iter().collect();
let joined = set1.join(&set2);
assert_eq!(joined, [1, 2, 3, 4].into_iter().collect());
let met = set1.meet(&set2);
assert!(met.is_empty());
}
#[test]
fn test_lattice_vec() {
let vec1 = vec![1, 2, 3];
let vec2 = vec![2, 3, 4];
let joined = vec1.join(&vec2);
assert_eq!(joined, vec![1, 2, 3, 4]);
let met = vec1.meet(&vec2);
assert_eq!(met, vec![2, 3]);
}
#[test]
fn test_lattice_vec_preserves_order() {
let vec1 = vec![3, 1, 2];
let vec2 = vec![4, 2, 1];
let joined = vec1.join(&vec2);
assert_eq!(joined, vec![3, 1, 2, 4]);
let met = vec1.meet(&vec2);
assert_eq!(met, vec![1, 2]);
}
#[test]
fn test_lattice_join_strategy_numeric() {
let dict1 = DoubleArrayTrie::from_terms_with_values(vec![("score", 85u32)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(vec![("score", 92u32)].into_iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LatticeJoin);
let score = union
.descend(b's')
.and_then(|z| z.descend(b'c'))
.and_then(|z| z.descend(b'o'))
.and_then(|z| z.descend(b'r'))
.and_then(|z| z.descend(b'e'))
.expect("Should find 'score'");
assert_eq!(score.value(), Some(92));
}
#[test]
fn test_lattice_meet_strategy_numeric() {
let dict1 = DoubleArrayTrie::from_terms_with_values(vec![("score", 85u32)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(vec![("score", 92u32)].into_iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LatticeMeet);
let score = union
.descend(b's')
.and_then(|z| z.descend(b'c'))
.and_then(|z| z.descend(b'o'))
.and_then(|z| z.descend(b'r'))
.and_then(|z| z.descend(b'e'))
.expect("Should find 'score'");
assert_eq!(score.value(), Some(85));
}
#[test]
fn test_lattice_join_strategy_hashset() {
let dict1 = DoubleArrayTrie::from_terms_with_values(
vec![("key", HashSet::from([1, 2]))].into_iter(),
);
let dict2 = DoubleArrayTrie::from_terms_with_values(
vec![("key", HashSet::from([2, 3]))].into_iter(),
);
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LatticeJoin);
let key = union
.descend(b'k')
.and_then(|z| z.descend(b'e'))
.and_then(|z| z.descend(b'y'))
.expect("Should find 'key'");
assert_eq!(key.value(), Some(HashSet::from([1, 2, 3])));
}
#[test]
fn test_lattice_meet_strategy_hashset() {
let dict1 = DoubleArrayTrie::from_terms_with_values(
vec![("key", HashSet::from([1, 2, 3]))].into_iter(),
);
let dict2 = DoubleArrayTrie::from_terms_with_values(
vec![("key", HashSet::from([2, 3, 4]))].into_iter(),
);
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LatticeMeet);
let key = union
.descend(b'k')
.and_then(|z| z.descend(b'e'))
.and_then(|z| z.descend(b'y'))
.expect("Should find 'key'");
assert_eq!(key.value(), Some(HashSet::from([2, 3])));
}
#[test]
fn test_lattice_join_three_dicts() {
let dict1 =
DoubleArrayTrie::from_terms_with_values(vec![("ctx", HashSet::from([1]))].into_iter());
let dict2 =
DoubleArrayTrie::from_terms_with_values(vec![("ctx", HashSet::from([2]))].into_iter());
let dict3 =
DoubleArrayTrie::from_terms_with_values(vec![("ctx", HashSet::from([3]))].into_iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let z3 = DoubleArrayTrieZipper::new_from_dict(&dict3);
let union = UnionZipper::with_strategy(vec![z1, z2, z3], LatticeJoin);
let ctx = union
.descend(b'c')
.and_then(|z| z.descend(b't'))
.and_then(|z| z.descend(b'x'))
.expect("Should find 'ctx'");
assert_eq!(ctx.value(), Some(HashSet::from([1, 2, 3])));
}
#[test]
fn test_lattice_meet_three_dicts() {
let dict1 = DoubleArrayTrie::from_terms_with_values(
vec![("ctx", HashSet::from([1, 2, 3, 4]))].into_iter(),
);
let dict2 = DoubleArrayTrie::from_terms_with_values(
vec![("ctx", HashSet::from([2, 3, 4, 5]))].into_iter(),
);
let dict3 = DoubleArrayTrie::from_terms_with_values(
vec![("ctx", HashSet::from([3, 4, 5, 6]))].into_iter(),
);
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let z3 = DoubleArrayTrieZipper::new_from_dict(&dict3);
let union = UnionZipper::with_strategy(vec![z1, z2, z3], LatticeMeet);
let ctx = union
.descend(b'c')
.and_then(|z| z.descend(b't'))
.and_then(|z| z.descend(b'x'))
.expect("Should find 'ctx'");
assert_eq!(ctx.value(), Some(HashSet::from([3, 4])));
}
#[test]
fn test_lattice_join_with_bool() {
let dict1 = DoubleArrayTrie::from_terms_with_values(vec![("flag", false)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(vec![("flag", true)].into_iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LatticeJoin);
let flag = union
.descend(b'f')
.and_then(|z| z.descend(b'l'))
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b'g'))
.expect("Should find 'flag'");
assert_eq!(flag.value(), Some(true));
}
#[test]
fn test_lattice_meet_with_bool() {
let dict1 = DoubleArrayTrie::from_terms_with_values(vec![("flag", false)].into_iter());
let dict2 = DoubleArrayTrie::from_terms_with_values(vec![("flag", true)].into_iter());
let z1 = DoubleArrayTrieZipper::new_from_dict(&dict1);
let z2 = DoubleArrayTrieZipper::new_from_dict(&dict2);
let union = UnionZipper::with_strategy(vec![z1, z2], LatticeMeet);
let flag = union
.descend(b'f')
.and_then(|z| z.descend(b'l'))
.and_then(|z| z.descend(b'a'))
.and_then(|z| z.descend(b'g'))
.expect("Should find 'flag'");
assert_eq!(flag.value(), Some(false));
}
}