use apache_datasketches::tuple::{
ArrayOfDoublesAnotB, ArrayOfDoublesSketch, ArrayOfDoublesSketchBuilder,
};
fn sketch(num_values: u8, keys: std::ops::Range<u64>, values: &[f64]) -> ArrayOfDoublesSketch {
let mut s = ArrayOfDoublesSketchBuilder::new()
.num_values(num_values)
.build()
.unwrap();
for key in keys {
s.update_u64(key, values).unwrap();
}
s
}
#[test]
fn a_not_b_both_empty_is_empty() {
let a = ArrayOfDoublesSketchBuilder::new().build().unwrap();
let b = ArrayOfDoublesSketchBuilder::new().build().unwrap();
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(result.is_empty());
}
#[test]
fn a_not_b_a_empty_is_empty() {
let a = ArrayOfDoublesSketchBuilder::new().build().unwrap();
let b = sketch(1, 0..1, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(result.is_empty());
}
#[test]
fn a_not_b_b_empty_returns_a() {
let a = sketch(2, 0..5, &[3.0, 4.0]);
let b = ArrayOfDoublesSketchBuilder::new()
.num_values(2)
.build()
.unwrap();
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert_eq!(result.get_estimate(), 5.0);
for (_, values) in result.entries() {
assert_eq!(values, [3.0, 4.0]);
}
}
#[test]
fn a_not_b_disjoint_sets_returns_a() {
let a = sketch(1, 0..5, &[1.0]);
let b = sketch(1, 5..10, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert_eq!(result.get_estimate(), 5.0);
}
#[test]
fn a_not_b_exact_partial_overlap_returns_exact_difference() {
let a = sketch(1, 0..1000, &[1.0]);
let b = sketch(1, 500..1500, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(!result.is_empty());
assert!(!result.is_estimation_mode());
assert_eq!(result.get_estimate(), 500.0);
}
#[test]
fn a_not_b_result_ordered_vs_unordered() {
let a = sketch(1, 0..1000, &[1.0]);
let b = sketch(1, 500..1500, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let ordered = a_not_b.compute(&a, &b, true).unwrap();
assert!(ordered.is_ordered());
assert_eq!(ordered.get_estimate(), 500.0);
let unordered = a_not_b.compute(&a, &b, false).unwrap();
assert!(!unordered.is_ordered());
assert_eq!(unordered.get_estimate(), 500.0);
assert_eq!(unordered.get_num_retained(), ordered.get_num_retained());
let result = a_not_b
.compute(&a.compact(true), &b.compact(true), true)
.unwrap();
assert!(result.is_ordered());
assert_eq!(result.get_estimate(), 500.0);
}
#[test]
fn a_not_b_self_is_always_empty() {
let s = sketch(1, 0..1000, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&s, &s, true).unwrap();
assert!(result.is_empty());
assert!(!result.is_estimation_mode());
assert_eq!(result.get_estimate(), 0.0);
let compact = s.compact(true);
let result = a_not_b.compute(&compact, &compact, true).unwrap();
assert!(result.is_empty());
assert!(!result.is_estimation_mode());
assert_eq!(result.get_estimate(), 0.0);
}
#[test]
fn a_not_b_estimation_mode_half_overlap_within_tolerance() {
let a = sketch(1, 0..10_000, &[1.0]);
let b = sketch(1, 5_000..15_000, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 5_000.0).abs() < 5_000.0 * 0.02);
let result = a_not_b
.compute(&a.compact(true), &b.compact(true), true)
.unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 5_000.0).abs() < 5_000.0 * 0.02);
}
#[test]
fn a_not_b_estimation_mode_disjoint_full_difference() {
let a = sketch(1, 0..10_000, &[1.0]);
let b = sketch(1, 10_000..20_000, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 10_000.0).abs() < 10_000.0 * 0.02);
let result = a_not_b
.compute(&a.compact(true), &b.compact(true), true)
.unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 10_000.0).abs() < 10_000.0 * 0.02);
}
#[test]
fn a_not_b_issue_152_large_size_mismatch() {
let a = sketch(1, 0..10_000, &[1.0]);
let b = sketch(1, 5_000..30_000, &[1.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 5_000.0).abs() < 5_000.0 * 0.03);
let result = a_not_b
.compute(&a.compact(true), &b.compact(true), true)
.unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 5_000.0).abs() < 5_000.0 * 0.03);
}
#[test]
fn a_not_b_estimation_mode_value_preservation() {
let a = sketch(2, 0..10_000, &[7.0, 8.0]);
let b = sketch(2, 10_000..20_000, &[7.0, 8.0]);
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(!result.is_empty());
assert!(result.is_estimation_mode());
assert!((result.get_estimate() - 10_000.0).abs() < 10_000.0 * 0.02);
let mut count = 0;
for (_, values) in result.entries() {
assert_eq!(values, [7.0, 8.0]);
count += 1;
}
assert!(count > 0, "expected at least one surviving entry");
}
#[test]
fn a_not_b_get_num_values_preserved_in_estimation_mode() {
let a = sketch(3, 0..10_000, &[1.0, 2.0, 3.0]);
let b = ArrayOfDoublesSketchBuilder::new()
.num_values(3)
.build()
.unwrap();
let a_not_b = ArrayOfDoublesAnotB::new();
let result = a_not_b.compute(&a, &b, true).unwrap();
assert!(result.is_estimation_mode());
assert_eq!(result.get_num_values(), 3);
}