vanrijn/src/util/interval.rs

329 lines
8.2 KiB
Rust

#[derive(Debug, Clone, Copy)]
pub struct Interval {
min: f64,
max: f64,
}
impl Interval {
pub fn new(a: f64, b: f64) -> Self {
if a > b {
Interval { min: b, max: a }
} else {
Interval { min: a, max: b }
}
}
pub fn empty() -> Self {
Interval {
min: f64::INFINITY,
max: f64::NEG_INFINITY,
}
}
pub fn infinite() -> Self {
Interval {
min: f64::NEG_INFINITY,
max: f64::INFINITY,
}
}
pub fn degenerate(value: f64) -> Self {
Interval {
min: value,
max: value,
}
}
pub fn get_min(&self) -> f64 {
self.min
}
pub fn get_max(&self) -> f64 {
self.max
}
pub fn is_degenerate(self) -> bool {
self.min == self.max
}
pub fn is_empty(self) -> bool {
self.min > self.max
}
pub fn contains_value(&self, value: f64) -> bool {
value >= self.min && value <= self.max
}
pub fn intersection(self, b: Self) -> Self {
Interval {
min: self.min.max(b.min),
max: self.max.min(b.max),
}
}
pub fn union(self, b: Self) -> Self {
if self.is_empty() {
b
} else if b.is_empty() {
self
} else {
Interval {
min: self.min.min(b.min),
max: self.max.max(b.max),
}
}
}
pub fn expand_to_value(self, v: f64) -> Self {
if self.is_empty() {
Interval::degenerate(v)
} else {
Interval {
min: self.min.min(v),
max: self.max.max(v),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use itertools::{Itertools, MinMaxResult};
use quickcheck_macros::quickcheck;
#[test]
fn never_constructed_empty() {
let target1 = Interval::new(5f64, 10f64);
assert!(!target1.is_empty());
let target2 = Interval::new(10f64, 5f64);
assert!(!target2.is_empty());
let target1 = Interval::new(5f64, -10f64);
assert!(!target1.is_empty());
let target2 = Interval::new(10f64, -5f64);
assert!(!target2.is_empty());
let target1 = Interval::new(-5f64, 10f64);
assert!(!target1.is_empty());
let target2 = Interval::new(-10f64, 5f64);
assert!(!target2.is_empty());
let target1 = Interval::new(-5f64, -10f64);
assert!(!target1.is_empty());
let target2 = Interval::new(-10f64, -5f64);
assert!(!target2.is_empty());
}
#[test]
fn empty_is_empty() {
let target: Interval = Interval::empty();
assert!(target.is_empty());
}
#[test]
fn empty_when_min_greater_than_max() {
let target = Interval {
min: 10f64,
max: 5f64,
};
assert!(target.is_empty());
}
#[test]
fn empty_when_min_greater_than_max_with_negative_values() {
let target = Interval {
min: -5f64,
max: -10f64,
};
assert!(target.is_empty());
}
#[test]
fn empty_when_min_greater_than_max_with_mixed_signs() {
let target = Interval {
min: 5f64,
max: -10f64,
};
assert!(target.is_empty());
}
#[test]
fn empty_is_not_degenerate() {
let target = Interval {
min: 10f64,
max: 5f64,
};
assert!(!target.is_degenerate());
}
#[test]
fn empty_is_not_degenerate_with_negative_values() {
let target = Interval {
min: -5f64,
max: -10f64,
};
assert!(!target.is_degenerate());
}
#[test]
fn empty_is_not_degenerate_with_mixed_signs() {
let target = Interval {
min: -5f64,
max: 10f64,
};
assert!(!target.is_degenerate());
}
#[quickcheck]
fn no_value_is_in_interval_returned_by_emtpy(value: f64) -> bool {
!Interval::empty().contains_value(value)
}
#[test]
fn identical_min_max_yields_degenerate() {
let target = Interval {
min: 5f64,
max: 5f64,
};
assert!(target.is_degenerate());
}
#[test]
fn degenerate_is_degenerate() {
let target = Interval::degenerate(5f64);
assert!(target.is_degenerate());
}
#[test]
fn degenerate_is_not_empty() {
let target = Interval {
min: 5f64,
max: 5f64,
};
assert!(target.is_degenerate());
}
#[test]
fn degenerate_is_degenerate_with_negative_value() {
let target = Interval {
min: -5f64,
max: -5f64,
};
assert!(target.is_degenerate());
}
#[test]
fn degenerate_is_not_empty_with_negative_value() {
let target = Interval {
min: -5f64,
max: -5f64,
};
assert!(target.is_degenerate());
}
#[test]
fn degenerate_contains_expected_value() {
let target = Interval::degenerate(5f64);
assert!(target.contains_value(5.0));
}
#[quickcheck]
fn degenerate_does_not_contain_any_values_othter_than_expected_value(value: f64) -> bool {
let target_value = if value == 5f64 { 5.5 } else { 5f64 };
let target = Interval::degenerate(target_value);
!target.contains_value(value)
}
#[test]
fn intersection_with_infinite_is_self() {
let target = Interval::new(5.0, 10.0);
let result = target.intersection(Interval::infinite());
assert!(target.min == result.min);
assert!(target.max == result.max);
}
#[quickcheck]
fn union_with_self_yields_self(a: f64, b: f64) -> bool {
let target = Interval::new(a, b);
let result = target.union(target);
result.min == target.min && result.max == target.max
}
#[quickcheck]
fn union_yields_min_and_max(a: f64, b: f64, c: f64, d: f64) -> bool {
let values = vec![a, b, c, d];
if let MinMaxResult::MinMax(&min, &max) =
values.iter().minmax_by(|a, b| a.partial_cmp(b).unwrap())
{
let target1 = Interval::new(a, b);
let target2 = Interval::new(c, d);
let result = target1.union(target2);
result.min == min && result.max == max
} else {
false
}
}
#[test]
fn union_with_empty_interval_is_correct() {
let empty = Interval {
min: 1f64,
max: -1f64,
};
let not_empty = Interval {
min: 5f64,
max: 10f64,
};
let union1 = not_empty.union(empty);
assert!(union1.min == 5.0);
assert!(union1.max == 10.0);
let union2 = empty.union(not_empty);
assert!(union2.min == 5.0);
assert!(union2.max == 10.0);
}
#[test]
fn union_with_empty_interval_is_correct_when_empty_interval_produced_by_intersection() {
let empty = Interval {
min: 1f64,
max: -1f64,
};
let not_empty = Interval {
min: 5f64,
max: 10f64,
};
let union1 = not_empty.union(empty);
assert!(union1.min == 5.0);
assert!(union1.max == 10.0);
let union2 = empty.union(not_empty);
assert!(union2.min == 5.0);
assert!(union2.max == 10.0);
}
#[quickcheck]
pub fn expand_to_value_creates_interval_that_includes_value(
min: f64,
max: f64,
value: f64,
) -> bool {
// Don't check if min <= max, we want to test empty intervals too
let interval1 = Interval { min, max };
let interval2 = interval1.expand_to_value(value);
interval2.contains_value(value)
}
#[quickcheck]
pub fn expand_to_value_creates_interval_that_includes_original_interval(
b: f64,
a: f64,
value: f64,
) -> bool {
let interval1 = Interval::new(a, b);
let interval2 = interval1.expand_to_value(value);
let interval3 = interval2.intersection(interval1);
// If interval2 contains interval1, that the intersection of the two will
// be equal to interval1
interval1.min == interval3.min && interval1.max == interval3.max
}
}