kvesteri / intervals

Python tools for handling intervals (ranges of comparable objects).
BSD 3-Clause "New" or "Revised" License
107 stars 17 forks source link

intervals

|Build Status| |Version Status| |Downloads|

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:

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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.

  1. Standard string format

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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.

.. code-block:: python

>>> from intervals import IntInterval

>>> interval = IntInterval.from_string('[1, ]')
>>> interval.lower
1
>>> interval.upper
inf
  1. Hyphenized format

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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.

Unbounded intervals

Unbounded intervals are intervals where either one of the bounds is infinite.

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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).

.. code-block:: python

>>> 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:

Properties

Emptiness

An interval is empty if it contains no points:

.. code-block:: python

>>> IntInterval.from_string('(1, 1]').empty
True

Data type coercion

Interval evaluates as True if its non-empty

.. code-block:: python

>>> 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

.. code-block:: python

>>> 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:

.. code-block:: python

IntInterval([1, 5]) > IntInterval([3, 3])

Its actually the same as typing:

.. code-block:: python

IntInterval([1, 5]) > [3, 3]

Which is also the same as typing:

.. code-block:: python

IntInterval([1, 5]) > 3

Comparison operators ^^^^^^^^^^^^^^^^^^^^

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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

.. code-block:: python

>>> 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.

.. code-block:: python

>>> 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.

.. code-block:: python

>>> from intervals import FloatInterval

>>> interval = FloatInterval([0.2, 0.8], step=0.5)
>>> interval.lower
0.0
>>> interval.upper
1.0

Arithmetics

Arithmetic operators ^^^^^^^^^^^^^^^^^^^^

.. code-block:: python

>>> Interval([1, 5]) + Interval([1, 8])
IntInterval('[2, 13]')

>>> Interval([1, 4]) - 1
IntInterval('[0, 3]')

Intersection:

.. code-block:: python

>>> Interval([2, 6]) & Interval([3, 8])
IntInterval('[3, 6]')

Union:

.. code-block:: python

>>> Interval([2, 6]) | Interval([3, 8])
IntInterval('[2, 8]')

Arithmetic functions ^^^^^^^^^^^^^^^^^^^^

.. code-block:: python

>>> 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]')

.. |Build Status| image:: https://travis-ci.org/kvesteri/intervals.png?branch=master :target: https://travis-ci.org/kvesteri/intervals .. |Version Status| image:: https://img.shields.io/pypi/v/intervals.svg :target: https://pypi.python.org/pypi/intervals/ .. |Downloads| image:: https://img.shields.io/pypi/dm/intervals.svg :target: https://pypi.python.org/pypi/intervals/