use yo_common::Small;
use yo_common::num::DIGITS_MAX;
use crate::intset::Walk;
use crate::set::{Limits, Needle, Set};
use crate::{Elements, Intset};
#[derive(Debug, Default)]
pub struct Scratch {
seen: Elements<()>,
counts: Elements<u32>,
}
impl Scratch {
#[must_use]
pub fn new() -> Scratch {
Scratch::default()
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.seen.memory_bytes() + self.counts.memory_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Plan {
Probe,
Accumulate,
Merge,
}
pub fn inter<F>(scratch: &mut Scratch, sets: &[&Set], limit: usize, f: F) -> usize
where
F: FnMut(&[u8]),
{
inter_with(scratch, plan_for(sets), sets, limit, f)
}
fn plan_for(sets: &[&Set]) -> Plan {
if sets.iter().all(|s| s.ints().is_some()) {
Plan::Merge
} else {
Plan::Probe
}
}
pub(crate) const INLINE_KEYS: usize = 8;
pub(crate) type PerSet<T> = Small<T, INLINE_KEYS>;
fn as_ints<'a>(sets: &[&'a Set]) -> Option<PerSet<&'a Intset>> {
sets.iter().map(|s| s.ints()).collect()
}
pub fn inter_with<F>(scratch: &mut Scratch, how: Plan, sets: &[&Set], limit: usize, f: F) -> usize
where
F: FnMut(&[u8]),
{
if sets.is_empty() || sets.iter().any(|s| s.is_empty()) {
return 0;
}
match how {
Plan::Merge => match as_ints(sets) {
Some(ints) => inter_merge(&ints, limit, f),
None => inter_probe(sets, limit, f),
},
Plan::Probe => inter_probe(sets, limit, f),
Plan::Accumulate => inter_accumulate(&mut scratch.counts, sets, limit, f),
}
}
fn inter_merge<F>(sets: &[&Intset], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let mut order: PerSet<usize> = (0..sets.len()).collect();
order.sort_unstable_by_key(|&i| sets[i].len());
let mut driver = sets[order[0]].walk();
let mut others: PerSet<Walk<'_>> = order[1..].iter().map(|&i| sets[i].walk()).collect();
let mut digits = [0u8; DIGITS_MAX];
let mut found = 0usize;
'members: while let Some(target) = driver.peek() {
for w in &mut others {
w.seek(target);
match w.peek() {
None => break 'members,
Some(v) if v > target => {
driver.seek(v);
continue 'members;
}
Some(_) => {}
}
}
f(yo_common::num::i64_digits(&mut digits, target));
found += 1;
if limit != 0 && found == limit {
break;
}
driver.bump();
}
found
}
fn inter_probe<F>(sets: &[&Set], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let mut order: PerSet<usize> = (0..sets.len()).collect();
order.sort_unstable_by_key(|&i| sets[i].len());
let (&first, rest) = order.split_first().expect("not empty");
let mut digits = [0u8; DIGITS_MAX];
let mut found = 0usize;
for m in sets[first].iter() {
let needle = Needle::of(m, &mut digits);
if rest.iter().all(|&i| sets[i].has(&needle)) {
f(needle.bytes());
found += 1;
if limit != 0 && found == limit {
break;
}
}
}
found
}
fn inter_accumulate<F>(seen: &mut Elements<u32>, sets: &[&Set], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let mut order: PerSet<usize> = (0..sets.len()).collect();
order.sort_unstable_by_key(|&i| sets[i].len());
let (&first, rest) = order.split_first().expect("not empty");
let mut digits = [0u8; DIGITS_MAX];
seen.clear();
yo_alloc::high_water(|| {
seen.reserve(sets[first].len());
for m in sets[first].iter() {
seen.insert(text(m, &mut digits), 1)
.expect("no larger than its source");
}
});
for &i in rest {
for m in sets[i].iter() {
if let Some(count) = seen.get_mut(text(m, &mut digits)) {
*count += 1;
}
}
}
let k = sets.len() as u32;
let mut found = 0usize;
for m in sets[first].iter() {
let name = text(m, &mut digits);
if seen.get(name) == Some(&k) {
f(name);
found += 1;
if limit != 0 && found == limit {
break;
}
}
}
found
}
#[inline]
fn text<'a>(m: crate::set::Member<'a>, digits: &'a mut [u8; DIGITS_MAX]) -> &'a [u8] {
match m {
crate::set::Member::Str(s) => s,
crate::set::Member::Int(n) => yo_common::num::i64_digits(digits, n),
}
}
pub fn union<F>(scratch: &mut Scratch, sets: &[&Set], limit: usize, f: F) -> usize
where
F: FnMut(&[u8]),
{
union_with(scratch, plan_for(sets), sets, limit, f)
}
pub fn union_with<F>(scratch: &mut Scratch, how: Plan, sets: &[&Set], limit: usize, f: F) -> usize
where
F: FnMut(&[u8]),
{
match (how, as_ints(sets)) {
(Plan::Merge, Some(ints)) if !ints.is_empty() => union_merge(&ints, limit, f),
_ => union_table(&mut scratch.seen, sets, limit, f),
}
}
fn union_merge<F>(sets: &[&Intset], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let mut walks: PerSet<Walk<'_>> = sets.iter().map(|s| s.walk()).collect();
let mut digits = [0u8; DIGITS_MAX];
let mut found = 0usize;
while let Some(low) = walks.iter().filter_map(Walk::peek).min() {
f(yo_common::num::i64_digits(&mut digits, low));
found += 1;
if limit != 0 && found == limit {
return found;
}
for w in &mut walks {
if w.peek() == Some(low) {
w.bump();
}
}
}
found
}
fn union_table<F>(seen: &mut Elements<()>, sets: &[&Set], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let biggest = sets.iter().map(|s| s.len()).max().unwrap_or(0);
let mut digits = [0u8; DIGITS_MAX];
seen.clear();
yo_alloc::high_water(|| seen.reserve(biggest));
let mut found = 0usize;
for s in sets {
for m in s.iter() {
let name = text(m, &mut digits);
let fresh = yo_alloc::high_water(|| seen.insert(name, ()));
if fresh.is_ok_and(|was| was.is_none()) {
f(name);
found += 1;
if limit != 0 && found == limit {
return found;
}
}
}
}
found
}
pub fn diff<F>(sets: &[&Set], limit: usize, f: F) -> usize
where
F: FnMut(&[u8]),
{
diff_with(plan_for(sets), sets, limit, f)
}
pub fn diff_with<F>(how: Plan, sets: &[&Set], limit: usize, f: F) -> usize
where
F: FnMut(&[u8]),
{
match (how, as_ints(sets)) {
(Plan::Merge, Some(ints)) if !ints.is_empty() => diff_merge(&ints, limit, f),
_ => diff_probe(sets, limit, f),
}
}
fn diff_merge<F>(sets: &[&Intset], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let (first, rest) = sets.split_first().expect("not empty");
let mut walk = first.walk();
let mut others: PerSet<Walk<'_>> = rest.iter().map(|s| s.walk()).collect();
let mut digits = [0u8; DIGITS_MAX];
let mut found = 0usize;
while let Some(v) = walk.peek() {
let mut anyone = false;
for w in &mut others {
w.seek(v);
if w.peek() == Some(v) {
anyone = true;
break;
}
}
if !anyone {
f(yo_common::num::i64_digits(&mut digits, v));
found += 1;
if limit != 0 && found == limit {
return found;
}
}
walk.bump();
}
found
}
fn diff_probe<F>(sets: &[&Set], limit: usize, mut f: F) -> usize
where
F: FnMut(&[u8]),
{
let Some((first, rest)) = sets.split_first() else {
return 0;
};
let mut order: PerSet<usize> = (0..rest.len()).collect();
order.sort_unstable_by_key(|&i| rest[i].len());
let mut digits = [0u8; DIGITS_MAX];
let mut found = 0usize;
for m in first.iter() {
let needle = Needle::of(m, &mut digits);
if !order.iter().any(|&i| rest[i].has(&needle)) {
f(needle.bytes());
found += 1;
if limit != 0 && found == limit {
return found;
}
}
}
found
}
pub fn collect(
upper: usize,
limits: &Limits,
run: impl FnOnce(&mut dyn FnMut(&[u8])),
) -> Option<Set> {
let mut out: Option<Set> = None;
run(&mut |name| match &mut out {
Some(s) => {
s.add(name, limits);
}
None => {
let mut s = Set::with_hint(name, upper, limits);
s.add(name, limits);
out = Some(s);
}
});
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::set::Encoding;
fn set(members: &[&str]) -> Set {
of(members.iter().map(|m| m.as_bytes()))
}
fn of<'a>(members: impl IntoIterator<Item = &'a [u8]>) -> Set {
let mut s = Set::new();
for m in members {
s.add(m, &Limits::DEFAULT);
}
s
}
type Band = (&'static str, fn(&[&str]) -> Set);
fn banded(members: &[&str], limits: &Limits) -> Set {
let mut s = Set::new();
for m in members {
s.add(m.as_bytes(), limits);
}
s
}
const AS_INTSET: Limits = Limits {
max_intset_entries: usize::MAX,
max_listpack_entries: usize::MAX,
max_listpack_value: usize::MAX,
};
const AS_LISTPACK: Limits = Limits {
max_intset_entries: 0,
max_listpack_entries: usize::MAX,
max_listpack_value: usize::MAX,
};
const AS_TABLE: Limits = Limits {
max_intset_entries: 0,
max_listpack_entries: 0,
max_listpack_value: 0,
};
fn tabled(members: &[&str]) -> Set {
let mut s = Set::new();
s.add(b"not a number", &AS_TABLE);
for m in members {
s.add(m.as_bytes(), &AS_TABLE);
}
s.remove(b"not a number");
assert_eq!(s.encoding(), Encoding::Hashtable);
assert!(s.ints().is_none(), "and a table underneath the word");
s
}
fn run<F>(op: F) -> Vec<String>
where
F: FnOnce(&mut dyn FnMut(&[u8])) -> usize,
{
let mut got = Vec::new();
let n = op(&mut |m| got.push(String::from_utf8_lossy(m).into_owned()));
assert_eq!(n, got.len(), "the count and the members disagree");
got
}
#[test]
fn an_intersection_is_what_they_all_have() {
let a = set(&["a", "b", "c", "d"]);
let b = set(&["b", "c", "d", "e"]);
let c = set(&["c", "d", "e", "f"]);
let got = run(|f| inter(&mut Scratch::new(), &[&a, &b, &c], 0, f));
assert_eq!(got, vec!["c", "d"]);
}
#[test]
fn an_intersection_of_one_set_is_that_set() {
let a = set(&["x", "y"]);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a], 0, f)),
vec!["x", "y"]
);
}
#[test]
fn an_empty_set_anywhere_empties_the_intersection() {
let a = set(&["a", "b"]);
let empty = set(&[]);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &empty], 0, f)),
Vec::<String>::new()
);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&empty, &a], 0, f)),
Vec::<String>::new()
);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[], 0, f)),
Vec::<String>::new()
);
}
#[test]
fn a_limit_stops_the_intersection_early() {
let a = set(&["a", "b", "c", "d", "e"]);
let b = set(&["a", "b", "c", "d", "e"]);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 2, f)),
vec!["a", "b"]
);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 99, f)).len(),
5
);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f)).len(),
5,
"zero is no limit"
);
}
#[test]
fn a_limit_stops_the_union_early() {
let a = set(&["1", "2", "3"]);
let b = set(&["3", "4", "5"]);
for how in [Plan::Merge, Plan::Accumulate] {
let mut s = Scratch::new();
assert_eq!(
run(|f| union_with(&mut s, how, &[&a, &b], 2, f)).len(),
2,
"{how:?} ignored the limit"
);
let mut s = Scratch::new();
assert_eq!(run(|f| union_with(&mut s, how, &[&a, &b], 99, f)).len(), 5);
let mut s = Scratch::new();
assert_eq!(
run(|f| union_with(&mut s, how, &[&a, &b], 0, f)).len(),
5,
"zero is no limit"
);
}
}
#[test]
fn a_limit_stops_the_difference_early() {
let a = set(&["1", "2", "3", "4", "5"]);
let b = set(&["5"]);
for how in [Plan::Merge, Plan::Probe] {
assert_eq!(
run(|f| diff_with(how, &[&a, &b], 2, f)).len(),
2,
"{how:?} ignored the limit"
);
assert_eq!(run(|f| diff_with(how, &[&a, &b], 99, f)).len(), 4);
assert_eq!(
run(|f| diff_with(how, &[&a, &b], 0, f)).len(),
4,
"zero is no limit"
);
}
}
#[test]
fn both_plans_give_the_same_answer_in_the_same_order() {
let sets: Vec<Set> = (0..9)
.map(|s| {
let members: Vec<String> = (0..200)
.filter(|i| i % (s + 2) != 1)
.map(|i| format!("m{i}"))
.collect();
set(&members.iter().map(String::as_str).collect::<Vec<_>>())
})
.collect();
let refs: Vec<&Set> = sets.iter().collect();
let probed = run(|f| inter_with(&mut Scratch::new(), Plan::Probe, &refs, 0, f));
let piled = run(|f| inter_with(&mut Scratch::new(), Plan::Accumulate, &refs, 0, f));
assert_eq!(probed, piled);
assert!(!probed.is_empty(), "the fixture should overlap");
assert_eq!(
run(|f| inter(&mut Scratch::new(), &refs, 0, f)),
probed,
"and so does the chooser"
);
}
#[test]
fn a_union_has_everything_once() {
let a = set(&["a", "b"]);
let b = set(&["b", "c"]);
let c = set(&["c", "d"]);
assert_eq!(
run(|f| union(&mut Scratch::new(), &[&a, &b, &c], 0, f)),
vec!["a", "b", "c", "d"]
);
assert_eq!(
run(|f| union(&mut Scratch::new(), &[], 0, f)),
Vec::<String>::new()
);
}
#[test]
fn a_difference_takes_the_others_out_of_the_first() {
let a = set(&["a", "b", "c", "d"]);
let b = set(&["b"]);
let c = set(&["d", "e"]);
assert_eq!(run(|f| diff(&[&a, &b, &c], 0, f)), vec!["a", "c"]);
assert_eq!(run(|f| diff(&[&a], 0, f)), vec!["a", "b", "c", "d"]);
assert_eq!(run(|f| diff(&[], 0, f)), Vec::<String>::new());
}
#[test]
fn the_plans_agree_where_every_set_holds_everything() {
let members: Vec<String> = (0..100).map(|i| format!("m{i}")).collect();
let names: Vec<&str> = members.iter().map(String::as_str).collect();
let sets: Vec<Set> = (0..10).map(|_| set(&names)).collect();
let refs: Vec<&Set> = sets.iter().collect();
let probed = run(|f| inter_with(&mut Scratch::new(), Plan::Probe, &refs, 0, f));
assert_eq!(probed, members, "everything is in all ten");
assert_eq!(
run(|f| inter_with(&mut Scratch::new(), Plan::Accumulate, &refs, 0, f)),
probed
);
assert_eq!(run(|f| inter(&mut Scratch::new(), &refs, 0, f)), probed);
}
#[test]
fn a_store_form_builds_a_set_of_the_result() {
let a = set(&["a", "b", "c"]);
let b = set(&["b", "c", "d"]);
let out = collect(a.len().min(b.len()), &Limits::DEFAULT, |f| {
inter(&mut Scratch::new(), &[&a, &b], 0, f);
})
.expect("two members is a set");
assert_eq!(out.len(), 2);
assert!(out.contains(b"b") && out.contains(b"c"));
assert!(!out.contains(b"a"));
}
#[test]
fn a_store_form_of_nothing_is_nothing() {
let a = set(&["a"]);
let b = set(&["b"]);
assert!(
collect(1, &Limits::DEFAULT, |f| {
inter(&mut Scratch::new(), &[&a, &b], 0, f);
})
.is_none()
);
}
#[test]
fn a_store_form_keeps_the_representation_its_members_deserve() {
let a = set(&["1", "2", "3"]);
let b = set(&["2", "3", "4"]);
assert_eq!(a.encoding(), Encoding::Intset);
let out = collect(3, &Limits::DEFAULT, |f| {
inter(&mut Scratch::new(), &[&a, &b], 0, f);
})
.expect("two members");
assert_eq!(out.encoding(), Encoding::Intset);
assert!(out.contains(b"2") && out.contains(b"3"));
let c = set(&["x"]);
let out = collect(4, &Limits::DEFAULT, |f| {
union(&mut Scratch::new(), &[&a, &c], 0, f);
})
.expect("four members");
assert_ne!(out.encoding(), Encoding::Intset);
assert!(out.contains(b"1") && out.contains(b"x"));
}
#[test]
fn the_three_representations_intersect_each_other() {
let names = ["1", "2", "3", "4"];
let others = ["3", "4", "5", "6"];
let bands: [Band; 3] = [
("intset", |m| banded(m, &AS_INTSET)),
("listpack", |m| banded(m, &AS_LISTPACK)),
("table", tabled),
];
for (ln, left) in bands {
for (rn, right) in bands {
let a = left(&names);
let b = right(&others);
let mut got = run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f));
got.sort();
assert_eq!(got, ["3", "4"], "{ln} against {rn}");
let mut got = run(|f| union(&mut Scratch::new(), &[&a, &b], 0, f));
got.sort();
assert_eq!(got, ["1", "2", "3", "4", "5", "6"], "{ln} with {rn}");
let mut got = run(|f| diff(&[&a, &b], 0, f));
got.sort();
assert_eq!(got, ["1", "2"], "{ln} without {rn}");
}
}
}
#[test]
fn a_number_and_its_untidy_spelling_stay_two_members() {
let a = banded(&["42", "042", "-0"], &AS_LISTPACK);
let b = banded(&["42"], &AS_INTSET);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f)),
vec!["42"]
);
let mut got = run(|f| diff(&[&a, &b], 0, f));
got.sort();
assert_eq!(got, ["-0", "042"]);
let mut got = run(|f| union(&mut Scratch::new(), &[&a, &b], 0, f));
got.sort();
assert_eq!(
got,
["-0", "042", "42"],
"and the union does not merge them"
);
}
fn ints(vals: &[i64]) -> Set {
let mut s = Set::new();
for v in vals {
s.add(v.to_string().as_bytes(), &AS_INTSET);
}
assert_eq!(s.encoding(), Encoding::Intset);
s
}
fn scattered(n: usize, seed: i64, span: i64) -> Vec<i64> {
(0..n as i64)
.map(|i| (i.wrapping_add(seed).wrapping_mul(2_654_435_761)).rem_euclid(span))
.collect()
}
#[test]
fn the_merge_and_the_probe_agree_on_every_shape() {
let shapes: [(&str, Vec<Vec<i64>>); 5] = [
(
"same size, mostly shared",
vec![scattered(4_000, 0, 5_000), scattered(4_000, 7, 5_000)],
),
(
"ten against a hundred thousand",
vec![scattered(10, 3, 100_000), scattered(100_000, 0, 200_000)],
),
(
"disjoint ranges",
vec![(0..2_000).collect(), (900_000..902_000).collect()],
),
(
"five sets",
vec![
scattered(3_000, 1, 4_000),
scattered(3_000, 2, 4_000),
scattered(3_000, 3, 4_000),
scattered(3_000, 4, 4_000),
scattered(3_000, 5, 4_000),
],
),
(
"negatives and a member too wide for a narrow run",
vec![
vec![-9_000_000_000, -3, -2, -1, 0, 1, 2, 9_000_000_000],
vec![-9_000_000_000, -2, 0, 2, 4, 9_000_000_000],
],
),
];
for (what, vals) in shapes {
let sets: Vec<Set> = vals.iter().map(|v| ints(v)).collect();
let refs: Vec<&Set> = sets.iter().collect();
assert_eq!(plan_for(&refs), Plan::Merge, "{what}");
let probed = run(|f| inter_with(&mut Scratch::new(), Plan::Probe, &refs, 0, f));
assert_eq!(
run(|f| inter(&mut Scratch::new(), &refs, 0, f)),
probed,
"intersect {what}"
);
assert_eq!(
run(|f| inter_with(&mut Scratch::new(), Plan::Accumulate, &refs, 0, f)),
probed,
"and the count agrees, {what}"
);
let subbed = diff_the_slow_way(&vals);
assert_eq!(run(|f| diff(&refs, 0, f)), subbed, "sub {what}");
assert_eq!(
run(|f| diff_with(Plan::Probe, &refs, 0, f)),
subbed,
"and the probe agrees, {what}"
);
let mut piled: Vec<String> = union_the_slow_way(&vals);
piled.sort();
for how in [Plan::Merge, Plan::Probe] {
let mut got = run(|f| union_with(&mut Scratch::new(), how, &refs, 0, f));
got.sort();
assert_eq!(got, piled, "union {what} by {how:?}");
}
}
}
fn diff_the_slow_way(vals: &[Vec<i64>]) -> Vec<String> {
let (first, rest) = vals.split_first().expect("not empty");
let others: std::collections::BTreeSet<i64> =
rest.iter().flat_map(|v| v.iter().copied()).collect();
let mut left: Vec<i64> = first
.iter()
.copied()
.filter(|v| !others.contains(v))
.collect();
left.sort_unstable();
left.dedup();
left.iter().map(i64::to_string).collect()
}
fn union_the_slow_way(vals: &[Vec<i64>]) -> Vec<String> {
let all: std::collections::BTreeSet<i64> =
vals.iter().flat_map(|v| v.iter().copied()).collect();
all.iter().map(i64::to_string).collect()
}
#[test]
fn a_merged_intersection_is_ascending_and_so_was_the_probe() {
let a = ints(&[900, 5, 40, 7, 1000, 3]);
let b = ints(&[1000, 3, 900, 8, 5]);
let got = run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f));
assert_eq!(got, vec!["3", "5", "900", "1000"]);
assert_eq!(
run(|f| inter_with(&mut Scratch::new(), Plan::Probe, &[&a, &b], 0, f)),
got
);
}
#[test]
fn a_limit_stops_a_merged_intersection_early() {
let vals: Vec<i64> = (0..2_000).collect();
let a = ints(&vals);
let b = ints(&vals);
assert_eq!(plan_for(&[&a, &b]), Plan::Merge);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 3, f)),
vec!["0", "1", "2"]
);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f)).len(),
2_000
);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a], 3, f)),
vec!["0", "1", "2"]
);
}
#[test]
fn one_unsorted_operand_takes_everything_back_to_a_probe() {
let a = ints(&[1, 2, 3]);
let b = tabled(&["2", "3", "4"]);
assert_eq!(plan_for(&[&a, &b]), Plan::Probe);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f)),
vec!["2", "3"]
);
assert_eq!(
run(|f| inter_with(&mut Scratch::new(), Plan::Merge, &[&a, &b], 0, f)),
vec!["2", "3"]
);
}
#[test]
fn a_set_past_the_intset_ceiling_still_merges() {
let a: Set = {
let mut s = Set::new();
for i in 0..5_000i64 {
s.add(i.to_string().as_bytes(), &Limits::DEFAULT);
}
s
};
assert_eq!(a.encoding(), Encoding::Hashtable, "the word a server uses");
assert!(a.ints().is_some(), "and an intset underneath it");
let b = ints(&[4_998, 4_999, 5_000]);
assert_eq!(plan_for(&[&a, &b]), Plan::Merge);
assert_eq!(
run(|f| inter(&mut Scratch::new(), &[&a, &b], 0, f)),
vec!["4998", "4999"]
);
}
#[test]
fn members_that_are_not_text_work_the_same() {
let a = of([&b"\x00\xff"[..], b"\xc3\x28", b""]);
let b = of([&b"\xc3\x28"[..], b""]);
let mut got: Vec<Vec<u8>> = Vec::new();
let n = inter(&mut Scratch::new(), &[&a, &b], 0, |m| got.push(m.to_vec()));
assert_eq!(n, 2);
assert_eq!(got, vec![b"\xc3\x28".to_vec(), b"".to_vec()]);
}
}