Expand description
§intervalsets: Intervals as Sets in Rust
Intervalsets intends to provide the full functionality of sets for interval data.
-
The
Intervaltype is a Set implementation representing a contiguous set of values.- It is generic over any type that implements the [
Domain] trait which is intended to make sure elements are comparable and allows us to differentiate between discrete and continuous data types.
- It is generic over any type that implements the [
-
The
IntervalSettype is a Set of disjoint, normalizedIntervalsmaintained in sorted order.
§Overview
Interval and IntervalSet are intended to be simple, versatile,
and correct. If you find any bugs, please open an issue on the repository
or open a pull request.
These types are immutable and as such can be easily be used in a
multi-threaded environment, or as keys in hash-structures as long as
the underlying generic type is Hash.
The vast majority of interactions with these Set types is governed by
trait implementations in the ops module.
§Getting Started
§Constructing Sets
use intervalsets::prelude::*;
use intervalsets::{Bound, Side};
let x = Interval::closed(0, 10);
assert_eq!(x.is_empty(), false);
assert_eq!(x.is_finite(), true);
assert_eq!(x.is_fully_bounded(), true);
assert_eq!(*x.right().unwrap(), Bound::closed(10));
assert_eq!(*x.rval().unwrap(), 10);
assert_eq!(format!("x = {}", x), "x = [0, 10]");
let x = Interval::closed_unbound(0.0);
assert_eq!(x.right(), None);
assert_eq!(x.is_half_bounded(), true);
assert_eq!(x.is_half_bounded_on(Side::Left), true);
let x = Interval::closed_open(-100.0, -50.0);
assert_eq!(*x.right().unwrap(), Bound::open(-50.0));
let y = Interval::convex_hull([5.0, 10.0, 23.0, -3.0, 22.0, 9.0, 99.9]);
assert_eq!(y, Interval::closed(-3.0, 99.9));
let iset = IntervalSet::from_iter([x, y]);
assert_eq!(iset.intervals().len(), 2);§Set Operations
use intervalsets::prelude::*;
let x = Interval::closed(0.0, 100.0);
let y = Interval::closed(1000.0, 1100.0);
let z = Interval::open(2000.0, 2100.0);
let a = Interval::<f64>::unbounded()
.difference(&x)
.difference(&y)
.difference(&z);
assert_eq!(a.contains(&50.0), false);
let b = x.union(&y).union(&z).complement();
assert_eq!(a, b);
let c = a.sym_difference(&b).expect_interval();
assert_eq!(c, Interval::<f64>::empty());§General Mapping
use intervalsets::prelude::*;
let x = Interval::closed(1, 5);
let y = x.flat_map_finite(|left, right| {
Interval::new_finite(left.map(|v| 10 * v), right.map(|v| 20 * v))
});
assert_eq!(y, Interval::closed(10, 100));
let z = IntervalSet::from_iter([x.clone(), y.clone()]);
let z = z.collect_map(|mut sets, subset| {
let mirror_image = subset.flat_map_finite(|left, right| {
Interval::new_finite(right.map(|v| -v), left.map(|v| -v))
});
sets.push(mirror_image);
sets.push(subset.clone());
});
assert_eq!(z, IntervalSet::from_iter([
x,
y,
Interval::closed(-5, -1),
Interval::closed(-100, -10),
]));§Measure of a Set
Two measures are provided.
They each return a Measurement which may be infinite.
§Width for continuous data types
use intervalsets::prelude::*;
let x = Interval::open(0.0, 10.0);
assert_eq!(x.width().finite(), 10.0);
let x = Interval::closed(0.0, 10.0);
assert_eq!(x.width().finite(), 10.0);
let x = Interval::closed_unbound(0.0);
assert_eq!(x.width().is_finite(), false);§Count for discrete data types
use intervalsets::prelude::*;
let x = Interval::closed(0, 10);
assert_eq!(x.count().finite(), 11);
let x = Interval::closed_unbound(0);
assert_eq!(x.count().is_finite(), false);§Optional Features
intervalsets has multiple Cargo features for controlling the underlying
data types used by Interval and IntervalSet. None are enabled by
default
- rust_decimal
- num-bigint
- chrono
- uom
§Custom Data Types
If you have a data type that is not currently supported by intervalsets
out of the box, there are a few traits that need to be implemented in order
to get started.
Firstly, does your library or application own the type you want to use? Yes? Great! Skip Ahead
§External Type Conflicts
Rust doesn’t allow us to implement traits for types if we don’t own at least one of them for fear that a future upstream change will introduce ambiguity. The solution to which is the New Type Idiom.
So, we need to proxy whatever type we want to work with.
use num_bigint::BigInt;
pub struct MyBigInt(BigInt);
// implement all the traits...§Required Custom Type Traits
intervalsets uses a handful of traits to fully define interval and set
behavior.
The
Domaintrait serves one purpose – to distinguish between types that represent discrete vs continuous data.This allows us to do two important things:
- Normalize discrete sets so that there is only a single valid representations of each possible
Set. eg. [1, 9] == (0, 10) == (0, 9] == [1, 10).- Properly test the adjacency of sets.
The method
try_adjacentis the mechanism by which both of these goals is accomplished. Implementations for continuous types should simply return None.The macro
continuous_domain_implexists for exactly this purpose.
The LibZero trait is a direct knock-off of
Zero. However, it is necessary to resolve issues of external traits and types. If a type already implementsZerothe macroadapt_num_traits_zero_implcan be used directly.The
LibZerotrait is necessary for themeasuremodule, specifically in regards to the empty set.
The
Countabletrait is only relevant to discrete data types. It is the mechanism by which a data type can say how many distinct values fall between some bounds of that type. There is a macrodefault_countable_implwhich usestry_adjacent.
The
AddandSubtraits are used by themeasuremodule, and could be used elsewhere in the future. Presumably these are already implemented for most types that one would want to use as bounds of a Set.
§Putting it all together
use core::ops::{Add, Sub};
use intervalsets::numeric::{Domain, LibZero};
use intervalsets::measure::Countable;
use intervalsets::Side;
// minimum required is: Clone, PartialEq, PartialOrd
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MyInt(i32);
impl Domain for MyInt {
fn try_adjacent(&self, side: Side) -> Option<Self> {
Some(match side {
Side::Left => Self(self.0 - 1),
Side::Right => Self(self.0 + 1),
})
}
}
// MyInt does not already implement num_traits::Zero
// so the adapt_num_traits_zero_impl doesn't help us here,
// even though i32 does.
impl LibZero for MyInt {
fn new_zero() -> Self {
Self(0)
}
}
// The default_countable_impl macro would work here just fine.
// This would be omitted for a continuous data type.
impl Countable for MyInt {
type Output = Self;
fn count_inclusive(left: &Self, right: &Self) -> Option<Self::Output> {
Some(Self(right.0 - left.0 + 1))
}
}
impl Add for MyInt {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Sub for MyInt {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}Modules§
- measure
- A Measure is a function of a set that gives a comparable size between sets.
- numeric
- Control behavior of data types that comprise the elements of a set.
- ops
- Operations on Set types.
- prelude
- Common operations & traits
Macros§
- adapt_
num_ traits_ zero_ impl - Create
LibZeroimpl(s) that delegate tonum_traits::Zero - continuous_
domain_ impl - Automatically implements
Domainfor a type. - default_
countable_ impl
Structs§
- Bound
- Defines the
Boundor limit that constrains a Set. - Interval
- A Set representation of a contiguous interval on N, Z, or R.
- Interval
Set - A Set in N, Z, or R consisting of disjoint contiguous intervals.
Enums§
- Bound
Type - The BoundType determines the inclusivity of the limit element in a set.
- Side
- Side( Left | Right ) on the number line.
Traits§
- Bounding
- The boundaries of a Set on the number line.
- Convex
Hull - Defines the minimal contiguous Interval which fully contains every provided item.
- Maybe
Empty - Defines an item that may be empty and a way to query it.