Intervals represent a contiguous set of elements. They are right-open by default
>>> from pyicl import Interval
>>> interval = Interval(0, 10)
>>> print interval
[0,10)
>>> 0 in interval
True
>>> 9 in interval
True
>>> 10 in interval
False
This is shown by square and round brackets in the string representation. The bounds are accessed via the bounds property.
>>> from pyicl import IntervalBounds
>>> print Interval(0, 3)
[0,3)
>>> print Interval(0, 3, IntervalBounds.LEFT_OPEN)
(0,3]
>>> print Interval(0, 3, IntervalBounds.CLOSED)
[0,3]
>>> print Interval(0, 3, IntervalBounds.OPEN)
(0,3)
>>> print Interval(0, 3, IntervalBounds.OPEN).bounds
OPEN
Empty intervals and intervals with exactly one element in them can be constructed
>>> Interval().empty
True
>>> 1 in Interval(1)
True
The bounds can be accessed as lower and upper
>>> Interval(0, 3).lower
0
>>> Interval(0, 3).upper
3
Intervals over discrete domains such as integers have first and last properties.
>>> from pyicl import IntInterval
>>> IntInterval(0, 3).first
0
>>> IntInterval(0, 3).last
2
Intervals are false if empty
>>> bool(Interval())
False
>>> Interval().empty
True
>>> bool(Interval(1, 10))
True
Element and interval membership is straightforward
>>> 1 in Interval(0, 2)
True
>>> 3 in Interval(0, 2)
False
>>> Interval(0, 2) in Interval(0, 2)
True
>>> Interval(0, 3) in Interval(0, 2)
False
Equivalence works as expected
>>> Interval(0, 2) == Interval(0, 2)
True
>>> Interval(0, 3) != Interval(0, 2)
True
Ordering works thus
>>> Interval(1, 3) < Interval(5, 6)
True
>>> Interval(1, 3) < Interval(1, 6)
True
>>> Interval(1, 6) < Interval(1, 3)
False
>>> Interval(5, 6) > Interval(1, 3)
True
>>> Interval(1, 3) < Interval(1, 5)
True
>>> Interval(1, 5) > Interval(1, 3)
True
>>> Interval(1, 3) <= Interval(5, 6)
True
>>> Interval(5, 6) >= Interval(1, 3)
True
>>> Interval(1, 3) <= Interval(1, 5)
True
>>> Interval(1, 5) >= Interval(1, 3)
True
>>> Interval(1, 3) > Interval(5, 6)
False
>>> Interval(5, 6) < Interval(1, 3)
False
>>> Interval(1, 3) > Interval(1, 5)
False
>>> Interval(1, 5) < Interval(1, 3)
False
There are also tests for exclusive ordering
>>> IntInterval(1, 3).exclusive_less(IntInterval(1, 5))
False
>>> IntInterval(1, 5).exclusive_less(IntInterval(5, 7))
True
>>> IntInterval(1, 5).lower_less(IntInterval(2, 7))
True
>>> IntInterval(1, 5).lower_less(IntInterval(1, 7))
False
>>> IntInterval(1, 5).lower_equal(IntInterval(1, 7))
True
>>> IntInterval(1, 5).lower_equal(IntInterval(0, 7))
False
>>> IntInterval(1, 5).lower_less_equal(IntInterval(2, 7))
True
>>> IntInterval(1, 5).lower_less_equal(IntInterval(0, 7))
False
>>> IntInterval(1, 5).upper_less(IntInterval(2, 7))
True
>>> IntInterval(1, 5).upper_less(IntInterval(1, 5))
False
>>> IntInterval(1, 5).upper_equal(IntInterval(1, 5))
True
>>> IntInterval(1, 5).upper_equal(IntInterval(0, 7))
False
>>> IntInterval(1, 5).upper_less_equal(IntInterval(2, 7))
True
>>> IntInterval(1, 5).upper_less_equal(IntInterval(0, 3))
False
Size comes in different forms. Discrete data types have a cardinality property
>>> from pyicl import FloatInterval
>>> IntInterval(1, 5).size
4
>>> IntInterval(1, 5).cardinality
4
>>> len(Interval(1, 5))
4
>>> len(FloatInterval(1, 5))
4
Intervals can be subtracted from the left or the right
>>> print Interval(1, 5).right_subtract(Interval(3, 5))
[1,3)
>>> print Interval(1, 5).left_subtract(Interval(1, 3))
[3,5)
Intervals can be intersected and tested for intersections and disjointedness
>>> print Interval(1, 5).intersection(Interval(0, 3))
[1,3)
>>> Interval(1, 3) == Interval(1, 5) & Interval(0, 3)
True
>>> interval = Interval(1, 5)
>>> interval &= Interval(0, 3)
>>> Interval(1, 3) == interval
True
>>> Interval(3, 5).intersects(Interval(1, 4))
True
>>> Interval(3, 5).intersects(Interval(1, 3))
False
>>> Interval(3, 5).disjoint(Interval(1, 4))
False
>>> Interval(3, 5).disjoint(Interval(1, 3))
True
Intervals can be tested whether they touch one another. “Touches” is not a reflexive operation.
>>> Interval(0, 3).touches(Interval(3, 5))
True
>>> Interval(0, 3).touches(Interval(4, 5))
False
>>> Interval(0, 3).touches(Interval(2, 5))
False
>>> Interval(3, 5).touches(Interval(0, 3))
False
The interval between two intervals can be calculated.
>>> print Interval(0, 3).inner_complement(Interval(6, 9))
[3,6)
>>> print Interval(6, 9).inner_complement(Interval(0, 3))
[3,6)
The distance between two intervals can be calculated.
>>> print Interval(0, 3).distance(Interval(6, 9))
3
>>> print Interval(6, 9).distance(Interval(0, 3))
3
An interval set is a set that is implemented as a set of elements. They can be constructed empty, with a single element, with an interval or with another interval set
>>> from pyicl import IntervalSet, Interval, IntervalSet
>>> IntervalSet().empty
True
>>> .1 in IntervalSet(.1)
True
>>> interval_set = IntervalSet(Interval(1, 9))
>>> 5 in interval_set
True
>>> 5 in IntervalSet(interval_set)
True
Iteration over intervals occurs at a segmental level. That is the members of the interval set are presented as distinct whole intervals
>>> for interval in IntervalSet(Interval(1, 9)).add(Interval(20, 29)):
... print interval
[1,9)
[20,29)
It is possible to look for the interval that an element is in
>>> print IntervalSet(Interval(1, 9)).find(5)
[1,9)
>>> print IntervalSet(Interval(1, 9)).find(10)
None
Similarly to intervals, there are tests for emptyness
>>> bool(IntervalSet())
False
>>> bool(IntervalSet(1))
True
>>> IntervalSet().empty
True
>>> IntervalSet(1).empty
False
Containedness is as expected
>>> 1 in IntervalSet(1)
True
>>> 2 in IntervalSet(1)
False
>>> 2 in IntervalSet(Interval(1, 9))
True
>>> Interval(1, 9) in IntervalSet(Interval(1, 9))
True
>>> IntervalSet(Interval(1, 9)) in IntervalSet(Interval(1, 9))
True
Equivalences and orderings are also as expected
>>> from pyicl import IntIntervalSet
>>> IntIntervalSet(1) == IntIntervalSet(1)
True
>>> IntIntervalSet(1) == IntIntervalSet(IntInterval(1))
True
>>> IntIntervalSet(1) == IntIntervalSet(IntInterval(1, 3))
False
>>> IntIntervalSet(1) != IntIntervalSet(IntInterval(1, 3))
True
>>> IntIntervalSet(1) < IntIntervalSet(IntInterval(1, 3))
True
>>> IntIntervalSet(1) > IntIntervalSet(IntInterval(1, 3))
False
Discrete data types have a size and cardinality. Interval sets also have an iterative size describing how many intervals they are composed of
>>> IntIntervalSet(IntInterval(1, 5)).size
4
>>> IntIntervalSet(IntInterval(1, 5)).cardinality
4
>>> len(IntIntervalSet(IntInterval(1, 5)))
4
>>> IntIntervalSet(IntInterval(1, 5)).iterative_size
1
>>> IntIntervalSet(IntInterval(1, 5)).add(IntInterval(7,10)).iterative_size
2
>>> IntIntervalSet(IntInterval(1, 5)).add(IntInterval(5,10)).iterative_size
1
>>> IntIntervalSet(IntInterval(1, 5)).interval_count
1
As with intervals, interval sets have bounds. Discrete data types also have a first and a last element
>>> IntervalSet(Interval(1, 5)).lower
1
>>> IntervalSet(Interval(1, 5)).upper
5
>>> IntIntervalSet(IntInterval(1, 5)).first
1
>>> IntIntervalSet(IntInterval(1, 5)).last
4
An interval map is a map (or in python-esque, a dict) that is implemented as a map of interval-value pairs. Each element has a particular value associated with it. The collection of elements is stored as a set of intervals such that each element of an interval maps to the same value.
The domain of an interval map is the domain of the intervals in the map. The codomain of an interval map is the data type of the values that the intervals map to. An element of an interval map is a pair mapping an element of the domain to a value in the codomain.
>>> from pyicl import IntervalMap
>>> print IntervalMap.Element(1, "value")
1; value
A segment of an interval map is a pair mapping an interval in the domain to a value in the codomain.
>>> print IntervalMap.Segment(Interval(1, 5), 10.4)
[1,5); 10.4
Interval maps can be constructed empty or from a segment
>>> IntervalMap().empty
True
>>> 3 in IntervalMap(IntervalMap.Segment(Interval(1, 5), object()))
True
Interval maps can be populated in several ways. The add method will combine values in the codomain.
>>> map = IntervalMap(IntervalMap.Segment(Interval(1, 5), "X"))
>>> print map.add(IntervalMap.Segment(Interval(3, 8), "Y"))
{([1,3)->X)([3,5)->XY)([5,8)->Y)}
+= acts in the same way as add.
>>> map = IntervalMap(IntervalMap.Segment(Interval(1, 5), "X"))
>>> map += (IntervalMap.Segment(Interval(3, 8), "Y"))
>>> print map
{([1,3)->X)([3,5)->XY)([5,8)->Y)}
The insert method only inserts into the map where there is no collision.
>>> map = IntervalMap(IntervalMap.Segment(Interval(1, 5), "X"))
>>> print map.insert(IntervalMap.Segment(Interval(3, 8), "Y"))
{([1,5)->X)([5,8)->Y)}
The set method will override values.
>>> print IntervalMap(IntervalMap.Segment(Interval(1, 5), "X")).set(IntervalMap.Element(3, "Y"))
{([1,3)->X)([3,3]->Y)((3,5)->X)}
If the codomain of a map supports subtraction, so does the map
>>> map = IntervalMap(IntervalMap.Segment(Interval(1, 5), 2))
>>> print map - IntervalMap.Segment(Interval(2, 4), 1)
{([1,2)->2)([2,4)->1)([4,5)->2)}
The erase method will remove values
>>> map = IntervalMap(IntervalMap.Segment(Interval(1, 5), 2))
>>> print map.erase(Interval(2, 4))
{([1,2)->2)([4,5)->2)}
Interval maps are presented as sequences of segments
>>> for segment in IntervalMap(IntervalMap.Segment(Interval(1, 5), "value1")).add(IntervalMap.Segment(Interval(11, 15), "value2")):
... print segment
[1,5); value1
[11,15); value2
The segment that contains a value in the domain can be found if it is in the map
>>> print IntervalMap(IntervalMap.Segment(Interval(1, 5), object())).find(2).interval
[1,5)
>>> print IntervalMap(IntervalMap.Segment(Interval(1, 5), object())).find(12)
None
A map can test whether it holds a particular value or interval in the domain, an element, a segment or another map
>>> map = IntervalMap(IntervalMap.Segment(Interval(1, 5), "X"))
>>> 2 in map
True
>>> Interval(1, 3) in map
True
>>> IntervalMap.Element(3, "X") in map
True
>>> IntervalMap.Element(8, "X") in map
False
>>> IntervalMap.Element(3, "Y") in map
False
>>> IntervalMap.Segment(Interval(1, 3), "X") in map
True
>>> IntervalMap.Segment(Interval(1, 8), "X") in map
False
>>> IntervalMap.Segment(Interval(1, 3), "Y") in map
False
>>> map in map
True
>>> IntervalMap(IntervalMap.Segment(Interval(1, 5), "X")) in map
True
>>> IntervalMap(IntervalMap.Segment(Interval(1, 5), "Y")) in map
False
Equality operators work as expected
>>> segment = IntervalMap.Segment(Interval(1, 5), "X")
>>> map == IntervalMap(segment)
True
>>> map != IntervalMap()
True
The size of an interval map is the number of elements it holds. The iterative size or count is how many segments it holds
>>> from pyicl import IntIntervalMap
>>> IntIntervalMap().size
0
>>> IntIntervalMap(IntIntervalMap.Segment(IntInterval(1, 5), "X")).size
4
>>> IntIntervalMap(IntIntervalMap.Segment(IntInterval(1, 5), "X")).cardinality
4
>>> len(IntIntervalMap(IntIntervalMap.Segment(IntInterval(1, 5), "X")))
4
>>> IntervalMap(IntervalMap.Segment(Interval(1, 5), "X")).iterative_size
1
>>> IntervalMap(IntervalMap.Segment(Interval(1, 5), "X")).interval_count
1
Intersections between maps and various other types are possible
>>> from pyicl import IntervalIntMap
>>> map = IntervalIntMap(IntervalIntMap.Segment(Interval(1, 5), 2))
>>> print map.intersection(Interval(2, 4))
{([2,4)->2)}
>>> print map.intersection(5)
{}
>>> print map.intersection(3)
{([3,3]->2)}
Symmetric difference or XOR is available
>>> from pyicl import IntervalIntMap
>>> map = IntervalIntMap(IntervalIntMap.Segment(Interval(3, 5), 2))
>>> print map ^ IntervalIntMap.Segment(Interval(0, 10), 4)
{([0,3)->4)([5,10)->4)}
>>> map ^= IntervalIntMap.Segment(Interval(0, 10), 4)
>>> print map
{([0,3)->4)([5,10)->4)}
Interval bounds can be open or closed on the left or right of the interval.
>>> from pyicl import IntervalBounds
>>> IntervalBounds.OPEN.closed_on_right
False
>>> IntervalBounds.OPEN.closed_on_left
False
>>> IntervalBounds.LEFT_OPEN.closed_on_right
True
>>> IntervalBounds.LEFT_OPEN.closed_on_left
False
>>> IntervalBounds.RIGHT_OPEN.closed_on_right
False
>>> IntervalBounds.RIGHT_OPEN.closed_on_left
True
>>> IntervalBounds.CLOSED.closed_on_right
True
>>> IntervalBounds.CLOSED.closed_on_left
True