kvesteri / intervals Goto Github PK
View Code? Open in Web Editor NEWPython tools for handling intervals (ranges of comparable objects).
License: BSD 3-Clause "New" or "Revised" License
Python tools for handling intervals (ranges of comparable objects).
License: BSD 3-Clause "New" or "Revised" License
I am getting the following unit tests errors:
=================================== FAILURES ===================================
______________ TestIntervalInit.test_string_as_constructor_param _______________
self = <tests.interval.test_initialization.TestIntervalInit object at 0xb69e718c>
def test_string_as_constructor_param(self):
with raises(TypeError) as e:
FloatInterval('(0.2, 0.5)')
> assert (
'First argument should be a list or tuple. If you wish to '
'initialize an interval from string, use from_string factory '
'method.'
) in str(e)
E AssertionError: assert 'First argument should be a list or tuple. If you wish to initialize an interval from string, use from_string factory method.' in '<ExceptionInfo TypeError tblen=2>'
E + where '<ExceptionInfo TypeError tblen=2>' = str(<ExceptionInfo TypeError tblen=2>)
tests/interval/test_initialization.py:22: AssertionError
____________________ TestIntervalInit.test_invalid_argument ____________________
self = <tests.interval.test_initialization.TestIntervalInit object at 0xb68ff0ec>
def test_invalid_argument(self):
with raises(IllegalArgument) as e:
FloatInterval((0, 0))
> assert (
'The bounds may be equal only if at least one of the bounds is '
'closed.'
) in str(e)
E AssertionError: assert 'The bounds may be equal only if at least one of the bounds is closed.' in '<ExceptionInfo IllegalArgument tblen=2>'
E + where '<ExceptionInfo IllegalArgument tblen=2>' = str(<ExceptionInfo IllegalArgument tblen=2>)
tests/interval/test_initialization.py:31: AssertionError
===================== 2 failed, 218 passed in 1.18 seconds =====================
Here are the packages I am using:
__eq__
is not symmetric
Interval.from_string('') == IntInterval.from_string('') # False
IntInterval.from_string('') == Interval.from_string('') # TypeError: Only discrete ranges can be canonicalized
Example:
from intervals import IntInterval
interval = IntInterval('(3, 3]')
interval.empty # True
interval = IntInterval([2, 4]) & IntInterval([5, 6])
isinstance(interval, IntInterval) # True
interval.empty # True
# https://pypi.org/project/intervals/
from intervals import Interval
The lower bound of the interval a = [1, 9]
is 1:
>>> a = Interval([1, 9])
>>> a.lower
1
The lower bound of the interval b = [2, 10]
is 2:
>>> b = Interval([2, 10])
>>> b.lower
2
The greatest of the lower bounds (1 and 2) is 2:
>>> max(a.lower, b.lower)
2
However, .glb()
returns an interval with a lower bound of 1 instead:
>>> a.glb(b)
IntInterval('[1, 9]')
>>> a.glb(b).lower
1
>>> b.glb(a)
IntInterval('[1, 9]')
>>> b.glb(a).lower
1
It also seems to always choose the least of the upper bounds (9), no matter in which order the operation is done:
>>> a.glb(b).upper
9
>>> b.glb(a).upper
9
The .lub()
method actually chooses the greatest lower bound (2) as well as the greatest upper bound (10):
>>> a.lub(b)
IntInterval('[2, 10]')
>>> a.lub(b).lower
2
>>> b.lub(a)
IntInterval('[2, 10]')
>>> b.lub(a).lower
2
The upper bound of the interval a = [1, 9]
is 9:
>>> a = Interval([1, 9])
>>> a.upper
9
The upper bound of the interval b = [2, 10]
is 10:
>>> b = Interval([2, 10])
>>> b.upper
10
The least of the upper bounds (10 and 9) is 9.
>>> min(a.upper, b.upper)
9
However, .lub()
returns an interval with an upper bound of 10 instead:
>>> a.lub(b)
IntInterval('[2, 10]')
>>> a.lub(b).upper
10
>>> b.lub(a)
IntInterval('[2, 10]')
>>> b.lub(a).upper
10
It also seems to always choose the greatest of the lower bounds (2), no matter in which order the operation is done:
>>> a.lub(b).lower
2
>>> b.lub(a).lower
2
The .glb() method actually gives the least upper bound (9) as well as the least lower bound (1):
>>> a.glb(b)
IntInterval('[1, 9]')
>>> a.glb(b).upper
9
>>> b.glb(a)
IntInterval('[1, 9]')
>>> b.glb(a).upper
9
Maybe exposing the inf in the intervals package is better, since you don't have to import from infinity package.
from intervals import Interval, inf
is cleaner, I think.
Running following codes, a type error will be raised:
DateInterval.from_string('[(2000-01-01),]')
self = <[AttributeError("'DateInterval' object has no attribute 'lower_inc'") raised in repr()] DateInterval object at 0x3730ef0>
value = '2000-01-01'
def coerce_string(self, value):
return self.type(value)
E TypeError: an integer is required (got type str)
Hi, I'm working on packaging this for ubuntu as it is used by sqlalchemy-utils but will have a hard time getting it into main (ie. officially supported) as it currently gives the appearance of having an unmaintained state. I realize that free software isn't free so just putting this out there. :) I know there isn't much new since 0.8.1, however a new release showing that this project supports modern Python 3 versions (we're up to 3.8 now) would be a good way to show that it is still alive and well. Also getting some responses to open issues would be nice to see too. Thanks for listening!
For example,
IntInterval(3, 5) == IntInterval((3, 5))
and,
IntInterval[3, 5] == IntInterval([3, 5])
I'm able to create an interval with the same lower and upper bound but one bound is inclusive and other other is exclusive. However this seems like an invalid interval to me because such abound seems to suggest that a value is both included and excluded from the interval.
As an example:
>>> Interval(1,1, lower_inc=True, upper_inc=False)
IntInterval('[1,1)')
Maybe my math needs improvement here, but if we have the following Integers intervals, the space between them is an empty interval so they should be connected.
>>> IntInterval.from_string('(1, 2]').is_connected(IntInterval.from_string('[3, 5]'))
False
from intervals import Interval
x = Interval([2, 3]) # Closed Interval
y = Interval([3, 4])
x & y
IntInterval('[3, 3]') # Correct answer
y = Interval((3, 4)) # Open interval
x = Interval((2, 3))
x & y
IntInterval('[3, 3]') # Should be [] because in open interval 3 won't be included
> a = FloatInterval((1,2))
> b = FloatInterval([0,3])
> a|b
FloatInterval('[0.0, 3.0]')
> b|a
FloatInterval('(0.0, 3.0)')
> a|b == b|a
False
Hello,
I have a problem with intervals and SQLAlchemy, I'm tracking changes to objects via the history, so when I try to insert a new DateRange it fails because it's comparing a DateInterval() object with a symbol('NO_VALUE'), which then tries to create a DateInterval("[symbol('NO_VALUE'), symbol('NO_VALUE')]") which of course fails with:
TypeError: unsupported operand type(s) for +: 'symbol' and 'datetime.timedelta'
Any ideas?
Verified both with Python 2 and Python 3.
If I want to check if a specific number is in an interval and the number type does not match that of the interval an exception is raised (check examples below).
I can except the case when float
is not in IntInterval
because the floating point numbers does not belong to the set of integers, but then the result should be False
not an exception.
As for 'int' in 'FloatInterval' as the real numbers set include the integer numbers set then it should return true no matter if we pass integer or float.
P.S. I would be happy to work on a fix )
>>> from intervals import IntInterval, FloatInterval
>>> ii = IntInterval.from_string('[0, 30)')
>>> 5 in ii
True
>>> 5.0 in ii
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/ilian/.virtualenvs/tmp-d04ae033510398ee/lib/python3.6/site-packages/intervals/interval.py", line 101, in wrapper
return func(self, arg)
File "/Users/ilian/.virtualenvs/tmp-d04ae033510398ee/lib/python3.6/site-packages/intervals/interval.py", line 438, in __contains__
if self.upper_inc or (not self.upper_inc and not other.upper_inc)
AttributeError: 'float' object has no attribute 'upper_inc'
>>>
And for FloatInterval
>>> fi = FloatInterval.from_string('[0, 30)')
>>> 5.0 in fi
True
>>> 5 in fi
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/ilian/.virtualenvs/tmp-d04ae033510398ee/lib/python3.6/site-packages/intervals/interval.py", line 101, in wrapper
return func(self, arg)
File "/Users/ilian/.virtualenvs/tmp-d04ae033510398ee/lib/python3.6/site-packages/intervals/interval.py", line 438, in __contains__
if self.upper_inc or (not self.upper_inc and not other.upper_inc)
AttributeError: 'int' object has no attribute 'upper_inc'
IntInterval.from_string('[0, 2)').length
should be 1
not 2
When I follow the example and use as follows:
from interval import Interval, IntervalSet
data = [(2, 4), (9, 13), (6, 12)]
intervals = IntervalSet([Interval.between(min, max) for min, max in data])
print ([(i.lower_bound, i.upper_bound) for i in intervals])
But it reported an error, and I haven’t found a solution to a similar problem for a long time:
Traceback (most recent call last):
File "/Users/zhongyongbin/Desktop/sapm/change-server/code/utils/handle_database/test_intercept.py", line 13, in <module>
intervals = IntervalSet([Interval(min, max) for min, max in data])
File "/usr/local/lib/python3.9/site-packages/interval.py", line 2065, in __init__
BaseIntervalSet.__init__(self, items)
File "/usr/local/lib/python3.9/site-packages/interval.py", line 922, in __init__
self._add(i)
File "/usr/local/lib/python3.9/site-packages/interval.py", line 1935, in _add
self.intervals.sort()
TypeError: '<' not supported between instances of 'Interval' and 'Interval'
use python 3.9
I'm not sure whether to report this here or sqlalchemy but here it is.
When updating a sqlalchemy object with a date_range field that was previously unset, it throws an OverflowError
in intervals coercion part.
Here's a replication.
import datetime
from intervals import DateInterval
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy_utils import DateRangeType
engine = create_engine('sqlite://')
Session = sessionmaker(bind=engine)
Base = declarative_base()
class Test(Base):
"""Example model utilizing dateinterval field"""
id = Column(Integer, primary_key=True)
date_range = Column(DateRangeType)
__tablename__ = 'test'
Base.metadata.create_all(engine)
session = Session()
test = Test()
session.add(test)
session.flush()
# Try to update the date_range field
date_interval = DateInterval.closed(datetime.date.today(), datetime.date.today())
test.date_range = date_interval
session.flush()
And here's the traceback
---------------------------------------------------------------------------
OverflowError Traceback (most recent call last)
<ipython-input-22-7c07ae3cd6ee> in <module>()
23 session.flush()
24 test.date_range = date_interval
---> 25 session.flush()
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/session.py in flush(self, objects)
2078 try:
2079 self._flushing = True
-> 2080 self._flush(objects)
2081 finally:
2082 self._flushing = False
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/session.py in _flush(self, objects)
2196 except:
2197 with util.safe_reraise():
-> 2198 transaction.rollback(_capture_exception=True)
2199
2200 def bulk_save_objects(
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/util/langhelpers.py in __exit__(self, type_, value, traceback)
58 exc_type, exc_value, exc_tb = self._exc_info
59 self._exc_info = None # remove potential circular references
---> 60 compat.reraise(exc_type, exc_value, exc_tb)
61 else:
62 if not compat.py3k and self._exc_info and self._exc_info[1]:
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/util/compat.py in reraise(tp, value, tb, cause)
185 if value.__traceback__ is not tb:
186 raise value.with_traceback(tb)
--> 187 raise value
188
189 else:
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/session.py in _flush(self, objects)
2160 self._warn_on_events = True
2161 try:
-> 2162 flush_context.execute()
2163 finally:
2164 self._warn_on_events = False
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/unitofwork.py in execute(self)
371 self.dependencies,
372 postsort_actions):
--> 373 rec.execute(self)
374
375 def finalize_flush_changes(self):
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/unitofwork.py in execute(self, uow)
530 uow.states_for_mapper_hierarchy(
531 self.mapper, False, False),
--> 532 uow
533 )
534
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py in save_obj(base_mapper, states, uowtransaction, single)
172 _emit_update_statements(base_mapper, uowtransaction,
173 cached_connections,
--> 174 mapper, table, update)
175
176 _emit_insert_statements(base_mapper, uowtransaction,
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py in _emit_update_statements(base_mapper, uowtransaction, cached_connections, mapper, table, update, bookkeeping)
636 records in groupby(
637 update,
--> 638 lambda rec: (
639 rec[4], # connection
640 set(rec[2]), # set of parameter keys
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py in _collect_update_commands(uowtransaction, table, states_to_update, bulk)
464 # objects for __eq__()
465 elif state.manager[propkey].impl.is_equal(
--> 466 value, state.committed_state[propkey]) is not True:
467 params[col.key] = value
468
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/sql/type_api.py in compare_values(self, x, y)
1088
1089 """
-> 1090 return self.impl.compare_values(x, y)
1091
1092 def __repr__(self):
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/sqlalchemy/sql/type_api.py in compare_values(self, x, y)
277 """Compare two values for equality."""
278
--> 279 return x == y
280
281 def get_dbapi_type(self, dbapi):
/Users/qria/.venvs/jupyter/lib/python3.6/site-packages/intervals/interval.py in wrapper(self, arg)
101 try:
102 arg = type(self)(self.type(arg))
--> 103 except (ValueError, TypeError):
104 pass
105 return func(self, arg)
OverflowError: signed integer is less than minimum
Looks like sqlalchemy sets a really big or really small integer value to previously unset value (to differentiate them with None
) in a form of sqlalchemy.util.symbol('NO_VALUE')
and intervals blows up trying to parse it.
This does not happen in 0.8.0
so we'll pin the version for the time being.
I get these results
In [8]: IntInterval((1, 2)) & IntInterval([1, 2])
Out[8]: IntInterval('[1, 2)')
In [9]: IntInterval([1, 2]) & IntInterval((1, 2))
Out[9]: IntInterval('(1, 2]')
I would expect these to be True
IntInterval((1, 2)) & IntInterval([1, 2]) == IntInterval((1, 2))
IntInterval([1, 2]) & IntInterval((1, 2)) == IntInterval((1, 2))
Nor 1 or 2 should be included in the intersection in my opinion (I am considering the intersection to be the elements that are present in both intervals), but in any case the intersection should be commutative.
Canonicalizing an unbounded interval changes the meaning of the interval.
As an example:
>>> a = IntInterval((None, None))
>>> infinity.inf in a
False
>>> b = canonicalize(a, lower_inc=True, upper_inc=True)
>>> b
IntInterval('[,]')
>>> infinity.inf in b
True
The initial version (a
) of the interval suggests that infinity is not included in the interval, but after the canonicalization infinity is in the interval.
There is an issue with how discrete intervals which are adjacent but don't share bound values are not unioned correctly.
As an example:
>>> IntInterval([1,2]) | IntInterval((2, 4))
IntInterval('[1, 4)')
>>> IntInterval((2,4)) == IntInterval((3,4), lower_inc=True)
True
>>> IntInterval([1,2]) | IntInterval((3,4), lower_inc=True)
IllegalArgument Traceback (most recent call last)
~/.../intervals/interval.py in __or__(self, other)
630 """
631 if not self.is_connected(other):
--> 632 raise IllegalArgument('Union is not continuous.')
633 lower = min(self.lower, other.lower)
634 upper = max(self.upper, other.upper)
IllegalArgument: Union is not continuous.
wtforms-components does:
from intervals import IntervalException
When issue 2 was fixed, that exception wasn't kept imported in the init.py, so one now has to:
from intervals.exc import IntervalException
which breaks existing code (I found it when fixing a bug in a flask based web site that I haven't touched in almost a year, when I recreated dev env locally using latest of all libs).
>>> from intervals import *
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'type' object does not support indexing
I'd like to see support for intervals which only define either a lower or upper boundary.
Examples include a shop article that is available only until a certain date (like a discontinued product), or only starting on a certain date (like a newly introduced product). The article would include both a lifetime start date and end date. Making it available for a limited period of time already works (using an interval with two boundaries) as would making it available forever (both start date and end date not being set; this doesn't have to be modeled as an interval, though, as it can be handled before doing any interval membership tests).
IntInterval(2, 5, inc_upper=True) == IntInterval('(2, 5]']
IntInterval(2, 5, inc_lower=True) == IntInterval('[2, 5)']
IntInterval(2, 5, inc_lower=True, inc_upper=True) == IntInterval[2, 5]
Hi, I catch error, when I do:
from intervals import DateInterval, inf
DateInterval(['2000-01-01', inf])
on 195 line in init you try make:
def coerce_string(self, value):
return self.type(value)
and self.type == datetime.date, and value == '2000-01-01' :)
I install intervals from pypi, v0.3.1
The current code allows the creation of an intervals where the borders are touching, which makes them unusable:
>>> 5 in IntInterval.from_string('[5, 5)')
False
In addition this leads to the fact that the intersection of touching intervals is such an "empty" interval:
>>> FloatInterval.from_string('[0, 5]') & FloatInterval.from_string('(5, 15]')
FloatInterval('(5.0, 5.0]')
I don't know if this is an issue, or if it is expected, but I was surprised at the performance difference between these two loops (2 orders of magnitude), so I thought I'd share:
import time
from intervals import IntInterval
myInterval = IntInterval('[2000,2500]')
start = time.time()
for x in range(0,1000):
if 2222 in myInterval: pass
if x in myInterval: pass
end = time.time()
print end - start
start = time.time()
for x in range(0,1000):
if 2222 >= myInterval.lower and 2222 <= myInterval.upper: pass
if x >= myInterval.lower and t <= myInterval.upper: pass
end = time.time()
print end - start
$ ./test_intervals_timing.py
0.0624339580536
0.000767946243286
This also means the objects should be immutable.
IntervalSet should support at least the following operations:
True
if given point / Interval / IntervalSet is contained within this IntervalSet)True
if the set represents the same interval as another value)True
if all the intervals in the set are open)True
if all the intervals are closed)str(IntInterval('[0, 0]')) # '(0, 0]'
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.