Python tools for handling intervals (ranges of comparable objects).
Interval initialization
Intervals can be initialized using the class constructor, various factory methods or from_string class method. The recommended way is to use the factory methods.
| Notation | Definition | Factory method |
|---|---|---|
| (a..b) | {x | a < x < b} | open |
| [a..b] | {x | a <= x <= b} | closed |
| (a..b] | {x | a < x <= b} | open_closed |
| [a..b) | {x | a <= x < b} | closed_open |
| (a..+∞) | {x | x > a} | greater_than |
| [a..+∞) | {x | x >= a} | at_least |
| (-∞..b) | {x | x < b} | less_than |
| (-∞..b] | {x | x <= b} | at_most |
| (-∞..+∞) | {x} | all |
When both endpoints exist, the upper endpoint may not be less than the lower. The endpoints may be equal only if at least one of the bounds is closed:
- [a..a]: a singleton range (contains only one value)
- [a..a), (a..a]: empty ranges
- (a..a): invalid; an
IllegalArgumentexception will be thrown
>>> from intervals import IntInterval >>> interval = IntInterval.open_closed(1, 5) >>> interval.lower 1 >>> interval.upper 5 >>> interval.upper_inc True >>> interval = IntInterval.all() >>> interval.lower -inf >>> interval.upper inf
The first argument of class constructor should define the bounds of the interval.
>>> from intervals import IntInterval >>> # All integers between 1 and 4 >>> interval = IntInterval([1, 4]) >>> interval.lower 1 >>> interval.upper 4 >>> interval.lower_inc True >>> interval.upper_inc True
You can also pass a scalar as the first constructor argument.
>>> from intervals import IntInterval >>> # All integers between 1 and 4 >>> interval = IntInterval(1) >>> interval.lower 1 >>> interval.upper 1
Initializing an interval from string
The from_string method accepts two different formats.
- Standard string format
>>> from intervals import IntInterval >>> # All integers between 1 and 4 >>> interval = IntInterval.from_string('[1, 4]') >>> interval.lower 1 >>> interval.upper 4
By using standard string format you can easily initialize half-open intervals.
>>> from intervals import IntInterval >>> interval = IntInterval.from_string('[1, 4)') >>> interval.lower 1 >>> interval.upper 4 >>> interval.upper_inc False
Unbounded intervals are supported as well.
>>> from intervals import IntInterval >>> interval = IntInterval.from_string('[1, ]') >>> interval.lower 1 >>> interval.upper inf
- Hyphenized format
>>> from intervals import IntInterval >>> # All integers between 1 and 4 >>> interval = IntInterval.from_string('1 - 4') >>> interval.lower 1 >>> interval.upper 4
You can also initialize unbounded ranges.
>>> from intervals import IntInterval >>> interval = IntInterval.from_string('1 - ') >>> interval.lower 1 >>> interval.upper inf
Open, half-open and closed intervals
Intervals can be either open, half-open or closed. Properties lower_inc and
upper_inc denote whether or not given endpoint is included (open) or not.
An open interval is an interval where both endpoints are open.
>>> interval = IntInterval((1, 4)) >>> interval.is_open True >>> interval.lower_inc False >>> interval.upper_inc False
Half-open interval has one of the endpoints as open
>>> from intervals import Interval >>> interval = IntInterval.from_string('[1, 4)') >>> interval.is_open False >>> interval.lower_inc True >>> interval.upper_inc False
Closed interval includes both endpoints
>>> interval = IntInterval.from_string('[1, 4]') >>> interval.is_closed True >>> interval.lower_inc True >>> interval.upper_inc True
Unbounded intervals
Unbounded intervals are intervals where either one of the bounds is infinite.
>>> from infinity import inf >>> from intervals import IntInterval >>> interval = IntInterval.closed_open(1, inf) >>> interval = IntInterval.open(-inf, inf)
Interval types
Each interval encapsulates a type. Interval is not actually a class. Its a
convenient factory that generates AbstractInterval subclasses. Whenever you
call Interval() the IntervalFactory tries to guess to best matching
interval for given bounds.
>>> from datetime import date >>> from infinity import inf >>> interval = Interval([1, 4]) >>> interval IntInterval('[1, 4]') >>> interval.type.__name__ 'int' >>> interval = Interval(['a', 'd']) >>> interval CharacterInterval('[a, d]') >>> interval.type.__name__ 'str' >>> interval = Interval([1.5, 4]) >>> interval FloatInterval('[1.5, 4.0]') >>> interval.type == type(5.5) True >>> interval = Interval([date(2000, 1, 1), inf]) >>> interval DateInterval('[2000-01-01,]') >>> interval.type.__name__ 'date'
You can also create interval subtypes directly (this is also faster than using
Interval).
>>> from intervals import FloatInterval, IntInterval >>> IntInterval([1, 4]) IntInterval('[1, 4]') >>> FloatInterval((1.4, 2.7)) FloatInterval('(1.4, 2.7)')
Currently provided subtypes are:
IntIntervalCharacterIntervalFloatIntervalDecimalIntervalDateIntervalDateTimeInterval
Properties
radiusgives the half-length of an interval>>> IntInterval([1, 4]).radius 1.5
lengthgives the length of an interval.>>> IntInterval([1, 4]).length 3
centregives the centre (midpoint) of an interval>>> IntInterval([-1, 1]).centre 0.0
Interval [a, b] is
degenerateif a = b>>> IntInterval([1, 1]).degenerate True >>> IntInterval([1, 2]).degenerate False
Emptiness
An interval is empty if it contains no points:
>>> IntInterval.from_string('(1, 1]').empty True
Data type coercion
Interval evaluates as True if its non-empty
>>> bool(IntInterval([1, 6])) True >>> bool(IntInterval([0, 0])) True >>> bool(IntInterval.from_string('(1, 1]')) False
Integer intervals can be coerced to integer if they contain only one point,
otherwise passing them to int() throws a TypeError
>>> int(IntInterval([1, 6])) Traceback (most recent call last): ... TypeError: Only intervals containing single point can be coerced to integers >>> int(IntInterval([1, 1])) 1
Operators
Operator coercion rules
All the operators and arithmetic functions use special coercion rules. These rules are made for convenience.
So for example when you type:
IntInterval([1, 5]) > IntInterval([3, 3])
Its actually the same as typing:
IntInterval([1, 5]) > [3, 3]
Which is also the same as typing:
IntInterval([1, 5]) > 3
Comparison operators
>>> IntInterval([1, 5]) > IntInterval([0, 3]) True >>> IntInterval([1, 5]) == IntInterval([1, 5]) True >>> IntInterval([2, 3]) in IntInterval([2, 6]) True >>> IntInterval([2, 3]) in IntInterval([2, 3]) True >>> IntInterval([2, 3]) in IntInterval((2, 3)) False
Intervals are hashable
Intervals are hashed on the same attributes that affect comparison: the values
of the upper and lower bounds, lower_inc and upper_inc, and the
type of the interval. This enables the use of intervals as keys in dict()
objects.
>>> IntInterval([3, 7]) in {IntInterval([3, 7]): 'zero to ten'} True >>> IntInterval([3, 7]) in set([IntInterval([3, 7])]) True >>> IntInterval((3, 7)) in set([IntInterval([3, 7])]) False >>> IntInterval([3, 7]) in set([FloatInterval([3, 7])]) False
Discrete intervals
>>> IntInterval([2, 4]) == IntInterval((1, 5)) True
Using interval steps
You can assign given interval to use optional step argument. By default
IntInterval uses step=1. When the interval encounters a value that is
not a multiplier of the step argument it tries to round it to the nearest
multiplier of the step.
>>> from intervals import IntInterval >>> interval = IntInterval([0, 5], step=2) >>> interval.lower 0 >>> interval.upper 6
You can also use steps for FloatInterval and DecimalInterval classes.
Same rounding rules apply here.
>>> from intervals import FloatInterval >>> interval = FloatInterval([0.2, 0.8], step=0.5) >>> interval.lower 0.0 >>> interval.upper 1.0
Arithmetics
Arithmetic operators
>>> Interval([1, 5]) + Interval([1, 8]) IntInterval('[2, 13]') >>> Interval([1, 4]) - 1 IntInterval('[0, 3]')
Intersection:
>>> Interval([2, 6]) & Interval([3, 8]) IntInterval('[3, 6]')
Union:
>>> Interval([2, 6]) | Interval([3, 8]) IntInterval('[2, 8]')
Arithmetic functions
>>> interval = IntInterval([1, 3]) >>> # greatest lower bound >>> interval.glb(IntInterval([1, 2])) IntInterval('[1, 2]') >>> # least upper bound >>> interval.lub(IntInterval([1, 2])) IntInterval('[1, 3]') >>> # infimum >>> interval.inf(IntInterval([1, 2])) IntInterval('[1, 2]') >>> # supremum >>> interval.sup(IntInterval([1, 2])) IntInterval('[1, 3]')