Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This library provides interval arithmetic for Python 2.7+ and Python 3.4+.
- Atomic intervals and interval sets are supported.
- Automatic simplification of intervals.
- Support iteration, comparison, intersection, union, complement, difference and containment.
- Import and export intervals to strings and to Python built-in data types.


## Installation
Expand Down Expand Up @@ -308,7 +309,7 @@ The `AtomicInterval` objects of an `Interval` can also be accessed using their i

```

### Import and export to string
### Import and export intervals to strings

Intervals can be exported to string, either using `repr` (as illustrated above) or with the `to_string` function.

Expand Down Expand Up @@ -348,8 +349,8 @@ A conversion function (`conv` parameter) has to be provided to convert a bound (
True
>>> I.from_string('[1.2]', conv=float) == I.singleton(1.2)
True
>>> from datetime import datetime
>>> converter = lambda s: datetime.strptime(s, '%Y/%m/%d')
>>> import datetime
>>> converter = lambda s: datetime.datetime.strptime(s, '%Y/%m/%d')
>>> I.from_string('[2011/03/15, 2013/10/10]', conv=converter)
[datetime.datetime(2011, 3, 15, 0, 0),datetime.datetime(2013, 10, 10, 0, 0)]

Expand Down Expand Up @@ -387,6 +388,38 @@ the `bound` parameter can be used to specify the regular expression that should
```


### Import and export intervals to a list of 4-uples

Intervals can also be exported to a list of 4-uples with `to_data`, e.g., to support JSON serialization.

```python
>>> x = I.openclosed(0, 1) | I.closedopen(2, I.inf)
>>> I.to_data(x)
[(False, 0, 1, True), (True, 2, inf, False)]

```

The function that is used to convert bounds can be specified with the `conv` parameter.
The values that must be used to represent positive and negative infinities can be specified with
`pinf` and `ninf`. They default to `float('inf')` and `float('-inf')` respectively.

```python
>>> x = I.closed(datetime.datetime(2011, 3, 15), datetime.datetime(2013, 10, 10))
>>> I.to_data(x, conv=lambda v: (v.year, v.month, v.day))
[(True, (2011, 3, 15), (2013, 10, 10), True)]

```

Intervals can be imported from such a list of 4-uples with `from_data`.
The same set of parameters can be used to specify how bounds and infinities are converted.

```python
>>> x = [(True, (2011, 3, 15), (2013, 10, 10), False)]
>>> I.from_data(x, conv=lambda v: datetime.datetime(*v))
[datetime.datetime(2011, 3, 15, 0, 0),datetime.datetime(2013, 10, 10, 0, 0))

```


## Contributions

Expand All @@ -404,6 +437,11 @@ Distributed under [LGPLv3 - GNU Lesser General Public License, version 3](https:
This library adheres to a [semantic versioning](https://semver.org) scheme.


**1.7.0** (2018-12-06)

- Import from and export to list of 4-uples with `from_data` and `to_data` ([#6](https://github.com/AlexandreDecan/python-intervals/issues/6)).


**1.6.0** (2018-08-29)

- Add support for customized infinity representation in `to_string` and `from_string` ([#3](https://github.com/AlexandreDecan/python-intervals/issues/3)).
Expand Down
71 changes: 69 additions & 2 deletions intervals.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re

__package__ = 'python-intervals'
__version__ = '1.6.0'
__version__ = '1.7.0'
__licence__ = 'LGPL3'
__author__ = 'Alexandre Decan'
__url__ = 'https://github.com/AlexandreDecan/python-intervals'
Expand All @@ -12,7 +12,7 @@
'inf', 'CLOSED', 'OPEN',
'Interval', # 'AtomicInterval',
'open', 'closed', 'openclosed', 'closedopen', 'singleton', 'empty',
'from_string', 'to_string',
'from_string', 'to_string', 'from_data', 'to_data',
]


Expand Down Expand Up @@ -214,6 +214,73 @@ def _convert(bound):
return disj.join(exported_intervals)



def from_data(data, conv=None, pinf=float('inf'), ninf=float('-inf')):
"""
Import an interval from a piece of data.

:param data: a list of 4-uples (left, lower, upper, right).
:param conv: function that is used to convert "lower" and "upper" to bounds, default to identity.
:param pinf: value used to represent positive infinity.
:param ninf: value used to represent negative infinity.
:return: an Interval instance.
"""
intervals = []
conv = (lambda v: v) if conv is None else conv

def _convert(bound):
if bound == pinf:
return inf
elif bound == ninf:
return -inf
else:
return conv(bound)

for item in data:
left, lower, upper, right = item
intervals.append(AtomicInterval(
left,
_convert(lower),
_convert(upper),
right
))
return Interval(*intervals)


def to_data(interval, conv=None, pinf=float('inf'), ninf=float('-inf')):
"""
Export given interval (or atomic interval) to a list of 4-uples (left, lower,
upper, right).

:param interval: an Interval or AtomicInterval instance.
:param conv: function that convert bounds to "lower" and "upper", default to identity.
:param pinf: value used to encode positive infinity.
:param ninf: value used to encode negative infinity.
:return:
"""
interval = Interval(interval) if isinstance(interval, AtomicInterval) else interval
conv = (lambda v: v) if conv is None else conv

data = []

def _convert(bound):
if bound == inf:
return pinf
elif bound == -inf:
return ninf
else:
return conv(bound)

for item in interval:
data.append((
item.left,
_convert(item.lower),
_convert(item.upper),
item.right
))
return data


class AtomicInterval:
"""
This class represents an atomic interval.
Expand Down
23 changes: 23 additions & 0 deletions test_intervals.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,29 @@ def test_from_string_customized():
assert I.from_string('<"0"-"1"> or <"2"-"3">', **params) == I.closed(0, 1) | I.closed(2, 3)


def test_to_data():
assert I.to_data(I.closedopen(2, 3)) == [(I.CLOSED, 2, 3, I.OPEN)]
assert I.to_data(I.openclosed(2, I.inf)) == [(I.OPEN, 2, float('inf'), I.OPEN)]
assert I.to_data(I.closed(-I.inf, 2)) == [(I.OPEN, float('-inf'), 2, I.CLOSED)]

i = I.openclosed(-I.inf, 4) | I.closedopen(6, I.inf)
assert I.to_data(i) == [(I.OPEN, float('-inf'), 4, I.CLOSED), (I.CLOSED, 6, float('inf'), I.OPEN)]
assert I.to_data(i, conv=str, pinf='highest', ninf='lowest') == [(I.OPEN, 'lowest', '4', I.CLOSED), (I.CLOSED, '6', 'highest', I.OPEN)]


def test_from_data():
assert I.from_data([(I.CLOSED, 2, 3, I.OPEN)]) == I.closedopen(2, 3)
assert I.from_data([(I.OPEN, 2, float('inf'), I.OPEN)]) == I.openclosed(2, I.inf)
assert I.from_data([(I.OPEN, float('-inf'), 2, I.CLOSED)]) == I.closed(-I.inf, 2)

d = [(I.OPEN, float('-inf'), 4, I.CLOSED), (I.CLOSED, 6, float('inf'), I.OPEN)]
assert I.from_data(d) == I.openclosed(-I.inf, 4) | I.closedopen(6, I.inf)

d = [(I.OPEN, 'lowest', '4', I.CLOSED), (I.CLOSED, '6', 'highest', I.OPEN)]
assert I.from_data(d, conv=int, pinf='highest', ninf='lowest') == I.openclosed(-I.inf, 4) | I.closedopen(6, I.inf)



def test_interval_to_atomic():
intervals = [I.closed(0, 1), I.open(0, 1), I.openclosed(0, 1), I.closedopen(0, 1)]
for interval in intervals:
Expand Down