use std::collections::BTreeMap;
#[derive(Clone, Debug, Default)]
pub(crate) struct FlakinessTracker {
votes: BTreeMap<usize, (usize, usize)>,
total_heads: usize,
total_tails: usize,
}
impl FlakinessTracker {
pub fn report(&mut self, index: usize, heads: bool) {
let value = self.votes.entry(index).or_insert((0, 0));
value.0 += if heads { 0 } else { 1 };
value.1 += if heads { 1 } else { 0 };
if heads {
self.total_heads += 1;
} else {
self.total_tails += 1;
}
}
pub fn inversions(&self) -> (usize, usize) {
let mut headstotal = 0;
let mut inverted = 0;
let mut random_inversions = 0;
let mut total_votes = 0;
for (tails, heads) in self.votes.values() {
let votes = heads + tails;
random_inversions += votes * votes + votes * total_votes;
inverted += tails * headstotal + tails * heads;
headstotal += heads;
total_votes += votes;
}
(inverted, random_inversions)
}
pub fn total_heads(&self) -> usize {
self.total_heads
}
pub fn total_tails(&self) -> usize {
self.total_tails
}
pub fn total_votes(&self) -> usize {
self.total_heads + self.total_tails
}
pub fn flakiness(&self) -> f64 {
let (inv, rand_inv) = self.inversions();
let tmp = 1.0 - (inv + 1) as f64 / (rand_inv as f64 / 4.0 + 4.0 / 3.0);
1.0 - tmp.max(0.0).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
let tracker = FlakinessTracker::default();
assert_eq!(tracker.inversions(), (0, 0));
assert!(
(tracker.flakiness() - 0.5).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn one_head() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, true);
assert_eq!(tracker.inversions(), (0, 1));
assert!(
(tracker.flakiness() - 0.3930).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn one_tail() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, true);
assert_eq!(tracker.inversions(), (0, 1));
assert!(
(tracker.flakiness() - 0.393).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn two_heads_same_bucket() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, true);
tracker.report(0, true);
assert_eq!(tracker.inversions(), (0, 4));
assert!(
(tracker.flakiness() - 0.2441).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn two_heads_different_buckets() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, true);
tracker.report(1, true);
assert_eq!(tracker.inversions(), (0, 3));
assert!(
(tracker.flakiness() - 0.2789).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn two_tails_same_bucket() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, false);
tracker.report(0, false);
assert_eq!(tracker.inversions(), (0, 4));
assert!(
(tracker.flakiness() - 0.2441).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn two_tails_different_buckets() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, false);
tracker.report(1, false);
assert_eq!(tracker.inversions(), (0, 3));
assert!(
(tracker.flakiness() - 0.2789).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn one_head_one_tail_same_bucket() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, false);
tracker.report(0, true);
assert_eq!(tracker.inversions(), (1, 4));
assert!(
(tracker.flakiness() - 0.622).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn one_head_one_tail_inverted() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, true);
tracker.report(1, false);
assert_eq!(tracker.inversions(), (1, 3));
assert!(
(tracker.flakiness() - 0.8).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn one_head_one_tail_not_inverted() {
let mut tracker = FlakinessTracker::default();
tracker.report(0, false);
tracker.report(1, true);
assert_eq!(tracker.inversions(), (0, 3));
assert!(
(tracker.flakiness() - 0.2789).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn hundred_heads_same_bucket() {
let mut tracker = FlakinessTracker::default();
for _ in 0..100 {
tracker.report(0, true);
}
assert_eq!(tracker.inversions(), (0, 10000));
assert!(
(tracker.flakiness() - 0.0002).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn hundred_heads_one_tail_same_bucket() {
let mut tracker = FlakinessTracker::default();
for _ in 0..100 {
tracker.report(0, true);
}
tracker.report(0, false);
assert_eq!(tracker.inversions(), (100, 10201));
assert!(
(tracker.flakiness() - 0.02).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn hundred_heads_hundred_tails_same_bucket() {
let mut tracker = FlakinessTracker::default();
for _ in 0..100 {
tracker.report(0, false);
tracker.report(0, true);
}
assert_eq!(tracker.inversions(), (10000, 40000));
assert!(
(tracker.flakiness() - 0.9942).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn hundred_heads_hundred_tails_inverted() {
let mut tracker = FlakinessTracker::default();
for _ in 0..100 {
tracker.report(0, true);
tracker.report(1, false);
}
assert_eq!(tracker.inversions(), (10000, 30000));
assert!(
(tracker.flakiness() - 0.9999).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
#[test]
fn hundred_heads_hundred_tails_not_inverted() {
let mut tracker = FlakinessTracker::default();
for _ in 0..100 {
tracker.report(0, false);
tracker.report(1, true);
}
assert_eq!(tracker.inversions(), (0, 30000));
assert!(
(tracker.flakiness() - 0.0001).abs() < 1e-4,
"flakiness = {}",
tracker.flakiness()
);
}
}