GithubHelp home page GithubHelp logo

kvesteri / intervals Goto Github PK

View Code? Open in Web Editor NEW
106.0 106.0 16.0 167 KB

Python tools for handling intervals (ranges of comparable objects).

License: BSD 3-Clause "New" or "Revised" License

Python 100.00%

intervals's People

Contributors

adarshk7 avatar arimbr avatar dogrover avatar evertrol avatar jpvanhal avatar kvesteri avatar philomelus avatar tvuotila avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

intervals's Issues

Unit test errors

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:

  • python = 3.7.3-1.2
  • appdirs = 1.4.3
  • atomicwrites = 1.3.0
  • attrs = 19.1.0
  • entrypoints = 0.3
  • flake8 = 3.7.8
  • importlib-metadata = 0.17
  • infinity = 1.4
  • isort = 4.3.21
  • mccabe = 0.6.1
  • more-itertools = 5.0.0
  • packaging = 19.0
  • pluggy = 0.12.0
  • py = 1.8.0
  • pycodestyle = 2.5.0
  • pyflakes = 2.1.1
  • pyparsing = 2.4.0
  • pytest = 5.0.1
  • setuptools = 41.0.1
  • six = 1.12.0
  • wcwidth = 0.1.7
  • zipp = 0.5.2

__eq__ is not symmetric

__eq__ is not symmetric

Interval.from_string('') == IntInterval.from_string('')  # False
IntInterval.from_string('') == Interval.from_string('')  # TypeError: Only discrete ranges can be canonicalized

Make support for empty intervals

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

.glb() and .lub() work in a confusing way

# https://pypi.org/project/intervals/
from intervals import Interval

Greatest lower bound

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

Least upper bound

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

Expose inf interface in this package

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.

date intervals cannot use from_string

Problem & Codes

Running following codes, a type error will be raised:

DateInterval.from_string('[(2000-01-01),]')

Error Details

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)

New release?

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!

Integer intervals are falsy reported as not connected

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

Intersection does not check if interval is open or closed

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

Problem using SQLAlchemy and calling history

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?

IntInterval and FloatInterval crash when input is not of the same type.

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'

TypeError: '<' not supported between instances of 'Interval' and 'Interval'

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

OverflowError in daterange coercion with sqlalchemy in intervals==0.8.1

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.

Intersection should be commutative

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

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.

Unioning Discrete Adjacent Intervals

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.

IntervalEception no longer in intervals namespace

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 *` doesn't work

>>> from intervals import *
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'type' object does not support indexing

Support for unbounded intervals

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

Error in DateInterval

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

Intersect returns empty intervals e.g. (5, 5] which are unusable.

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

checking 'in' interval performance

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

Support for IntervalSets (discontinous intervals)

IntervalSet should support at least the following operations:

  • union
  • intersection
  • length (sum of the lengths of all intervals in this set)
  • radius (length / 2)
  • degenerate
  • empty / bool / nonzero
  • discrete
  • contains (True if given point / Interval / IntervalSet is contained within this IntervalSet)
  • eq (True if the set represents the same interval as another value)
  • ne
  • open (True if all the intervals in the set are open)
  • closed (True if all the intervals are closed)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.