The main classes in the pyicl package are Intervals, IntervalSets and IntervalMaps.
In this section we demonstrate basic usage of the PyICL module. We follow the examples given in the Boost Interval Container Library documentation. First import the module:
>>> import pyicl
Intervals are half-open by default:
>>> interval = pyicl.Interval(0, 10)
>>> print interval
[0,10)
>>> print 0 in interval
True
>>> print 10 in interval
False
Interval sets and maps can be viewed in 2 complementary ways or aspects. The first aspect treats interval sets and maps as containers of elements and is called the fundamental aspect. The second aspect treats interval sets and maps as containers of intervals (or segments) and is called the segmental aspect. The fundamental aspect supports inserting elements into a set or map and testing for their presence:
>>> myset = pyicl.IntervalSet()
>>> myset += 42
>>> print 42 in myset
True
The segmental aspect supports efficiently iterating over a interval set or map. We would rather not visit each element, we would rather visit whole segments at a time:
>>> from datetime import time
>>> news = pyicl.Interval(time(20, 00, 00), time(20, 15, 00))
>>> talk = pyicl.Interval(time(22, 45, 30), time(23, 30, 50))
>>> myTvPrograms = pyicl.IntervalSet()
>>> myTvPrograms.add(news).add(talk)
<pyicl...IntervalSet object at 0x...>
>>> for segment in myTvPrograms:
... print segment
[20:00:00,20:15:00)
[22:45:30,23:30:50)
We can test for an element’s inclusion in an interval set just as we can in an interval:
>>> time(20, 10, 00) in myTvPrograms
True
>>> time(21, 00, 00) in myTvPrograms
False
>>> time(23, 00, 00) in myTvPrograms
True
For interval sets, addability is implemented as set union and subtractability is implemented as set difference:
>>> interval1 = pyicl.Interval( 0, 10)
>>> interval2 = pyicl.Interval( 5, 15)
>>> interval3 = pyicl.Interval(20, 30)
>>> intervalsetA = pyicl.IntervalSet()
>>> intervalsetA += interval1
>>> intervalsetA += interval3
>>> print intervalsetA
{[0,10)[20,30)}
>>> intervalsetB = pyicl.IntervalSet()
>>> intervalsetB += interval2
>>> print intervalsetB
{[5,15)}
>>> print intervalsetA + intervalsetB
{[0,15)[20,30)}
>>> print intervalsetA - intervalsetB
{[0,5)[20,30)}
>>> print intervalsetB - intervalsetA
{[10,15)}
For interval maps, addability and subtractability are more interesting, especially when elements of the two maps collide:
>>> map = pyicl.IntIntervalMap()
>>> map += map.Segment(pyicl.IntInterval(0,10), 1)
>>> map += map.Segment(pyicl.IntInterval(5,15), 2)
>>> for segment in map:
... print segment
[0,5); 1
[5,10); 3
[10,15); 2
>>> map -= map.Segment(pyicl.IntInterval(5,15), 2)
>>> for segment in map:
... print segment
[0,10); 1
[10,15); 0
Here we use an example of a party to demonstrate the aggregate on overlap principle on IntervalMaps.
>>> from datetime import time
>>> map = pyicl.IntervalMap()
>>> map += map.Segment(pyicl.Interval(time(20,00), time(22,00)), pyicl.Set(['Mary',]))
>>> map += map.Segment(pyicl.Interval(time(21,00), time(23,00)), pyicl.Set(['Harry',]))
>>> for segment in map:
... print segment
[20:00:00,21:00:00); Set(['Mary'])
[21:00:00,22:00:00); Set(['Mary', 'Harry'])
[22:00:00,23:00:00); Set(['Harry'])
Here we have used the pyicl.Set class to store the guests at the party. This is important as the default python set class does not interact well with the pyicl package.
We may wish to perform other operations on aggregation. For example, we could store the guests heights rather than their names and use the pyicl package to infer the maximum height of the guests at the party at any given time.
>>> map = pyicl.IntervalMap()
>>> map += map.Segment(pyicl.Interval(time(20,00), time(22,00)), pyicl.Max(1.72))
>>> map += map.Segment(pyicl.Interval(time(21,00), time(23,00)), pyicl.Max(1.85))
>>> for segment in map:
... print segment
[20:00:00,21:00:00); 1.72
[21:00:00,23:00:00); 1.85
In the previous example we used the pyicl.Max class that implements += as maximisation. We could also define a custom class that performs minimisation or any other operation we care to use.
>>> class Min(object):
... def __init__(self, value):
... self.value = value
... def __iadd__(self, other):
... return Min(min(self.value, other.value))
... def __eq__(self, other):
... return None != other and self.value == other.value
... def __str__(self):
... return str(self.value)
>>> map = pyicl.IntervalMap()
>>> map += map.Segment(pyicl.Interval(time(20,00), time(22,00)), Min(1.72))
>>> map += map.Segment(pyicl.Interval(time(21,00), time(23,00)), Min(1.85))
>>> for segment in map:
... print segment
[20:00:00,22:00:00); 1.72
[22:00:00,23:00:00); 1.85
The pyicl package supplies an Aggregator class to simplify this. The above code can be replaced by
>>> map = pyicl.IntervalMap()
>>> map += map.Segment(pyicl.Interval(time(20,00), time(22,00)), pyicl.Aggregator(1.72, min))
>>> map += map.Segment(pyicl.Interval(time(21,00), time(23,00)), pyicl.Aggregator(1.85, min))
>>> for segment in map:
... print segment
[20:00:00,22:00:00); 1.72
[22:00:00,23:00:00); 1.85
There is also a slightly simpler alternative with the drawback that the values are not pickle-able.
>>> MyAggregator = pyicl.make_aggregator(min)
>>> map = pyicl.IntervalMap()
>>> map += map.Segment(pyicl.Interval(time(20,00), time(22,00)), MyAggregator(1.72))
>>> map += map.Segment(pyicl.Interval(time(21,00), time(23,00)), MyAggregator(1.85))
>>> for segment in map:
... print segment
[20:00:00,22:00:00); 1.72
[22:00:00,23:00:00); 1.85
When we add intervals or interval value pairs to interval containers, the intervals can be added in different ways: Intervals can be joined or split or kept separate. The different interval combining styles are shown by example.
Joining intervals are the default and are joined on overlap or touch.
>>> myset = pyicl.IntervalSet()
>>> myset += pyicl.Interval(0, 10)
>>> myset += pyicl.Interval(5, 15)
>>> for interval in myset:
... print interval
[0,15)
Splitting intervals are are split on overlap. All interval borders are preserved.
>>> myset = pyicl.SplitIntervalSet()
>>> myset += pyicl.Interval(0, 10)
>>> myset += pyicl.Interval(5, 15)
>>> for interval in myset:
... print interval
[0,5)
[5,10)
[10,15)
Separating intervals are joined on overlap, not on touch.
>>> myset = pyicl.SeparateIntervalSet()
>>> myset += pyicl.Interval(0, 10)
>>> myset += pyicl.Interval(5, 15)
>>> myset += pyicl.Interval(15, 20)
>>> for interval in myset:
... print interval
[0,15)
[15,20)