use std::f64::consts::{E, PI};
pub fn estimate_multi_value_selection_cardinality(
total_points: usize,
total_values: usize,
selected_values_count: usize,
) -> f64 {
assert!(total_values >= total_points);
let values_per_point = total_values as f64 / total_points as f64;
let prob_select = 1. - prob_not_select(total_values, values_per_point, selected_values_count);
prob_select * total_points as f64
}
fn approx_fact_log(n: f64) -> f64 {
if n < 1.0 {
return 1.0; }
(2. * PI * n).sqrt().ln() + n * (n / E).ln()
}
fn prob_not_select(total: usize, avg: f64, selected: usize) -> f64 {
let total = total as f64;
let selected = selected as f64;
(approx_fact_log(total - avg) + approx_fact_log(total - selected)
- approx_fact_log(total - avg - selected)
- approx_fact_log(total))
.exp()
}
pub fn number_of_selected_points(points: usize, values: usize) -> usize {
let prob_of_selection = 1. - (-(values as f64 / points as f64)).exp();
(prob_of_selection * points as f64).round() as usize
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use rand::SeedableRng;
use rand::prelude::StdRng;
use rand::seq::SliceRandom;
use super::*;
#[test]
fn test_selected_points_est() {
let res = number_of_selected_points(100, 1000);
assert!(res > 95);
assert!(res <= 100);
let res = number_of_selected_points(1000, 10);
assert!(res > 5);
assert!(res <= 10);
}
fn simulate(uniq: usize, avg: usize, selected: usize) -> usize {
let mut data: Vec<_> = vec![];
for i in 0..uniq {
for _ in 0..avg {
data.push(i);
}
}
data.shuffle(&mut StdRng::seed_from_u64(42));
let mut unique_selected: HashSet<_> = Default::default();
for x in data.into_iter().take(selected) {
unique_selected.insert(x);
}
unique_selected.len()
}
#[test]
fn approx_factorial() {
let approx = approx_fact_log(10.).exp();
let real = f64::from(2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10);
let error = (approx / real - 1.0).abs();
assert!(error < 0.01);
}
#[test]
fn test_estimation_corner_cases() {
let count = estimate_multi_value_selection_cardinality(10, 20, 20);
assert!(!count.is_nan());
eprintln!("count = {count:#?}");
let count = estimate_multi_value_selection_cardinality(100, 100, 100);
assert!(!count.is_nan());
eprintln!("count = {count:#?}");
let count = estimate_multi_value_selection_cardinality(100, 100, 50);
assert!(!count.is_nan());
eprintln!("count = {count:#?}");
let count = estimate_multi_value_selection_cardinality(10, 10, 10);
assert!(!count.is_nan());
eprintln!("count = {count:#?}");
let count = estimate_multi_value_selection_cardinality(1, 1, 1);
assert!(!count.is_nan());
eprintln!("count = {count:#?}");
let count = estimate_multi_value_selection_cardinality(1, 1, 0);
assert!(!count.is_nan());
eprintln!("count = {count:#?}");
}
#[test]
fn test_estimation_1() {
let total = 2000;
let unique = 1000;
let selected = 50;
let estimation = estimate_multi_value_selection_cardinality(unique, total, selected);
let experiment = simulate(unique, total / unique, selected);
let error = (estimation / experiment as f64 - 1.0).abs();
assert!(error < 0.05);
}
#[test]
fn test_estimation_2() {
let total = 2000;
let unique = 1000;
let selected = 300;
let estimation = estimate_multi_value_selection_cardinality(unique, total, selected);
let experiment = simulate(unique, total / unique, selected);
let error = (estimation / experiment as f64 - 1.0).abs();
assert!(error < 0.05);
}
}