Reputation: 337
I want to make a Python class for intervals of real numbers. Syntax most closely related to mathematical notation would be Interval([a, b))
or, even better, Interval[a, b)
to construct the interval of all real x
satisfying a <= x < b
.
Is it possible to construct a class that would handle this syntax?
Upvotes: 6
Views: 12946
Reputation: 387
You can use the pandas Interval
object.
Example:
import pandas as pd
my_interval = pd.Interval(0.5, 1.5, closed="left")
print(my_interval)
Output:
[0.5, 1.5)
Upvotes: 0
Reputation: 3893
It's impossible to "fix" syntactically invalid python by making a custom class.
I think the closest you can get to the mathematical interval notation in python is
Interval('[a, b)')
This way becomes even more lightweight if you are passing intervals as arguments to a function and the function converts it's arguments to an appropriate type before using them. Example:
def do_foo(interval, bar, baz):
interval = Interval(interval)
# do stuff
do_foo('[3,4)', 42, true)
import re
class Interval:
def __init__(self, interval):
"""Initialize an Interval object from a string representation of an interval
e.g: Interval('(3,4]')"""
if isinstance(interval, Interval):
self.begin, self.end = interval.begin, interval.end
self.begin_included = interval.begin_included
self.end_included = interval.end_included
return
number_re = '-?[0-9]+(?:.[0-9]+)?'
interval_re = ('^\s*'
+'(\[|\()' # opeing brecket
+ '\s*'
+ '(' + number_re + ')' # beginning of the interval
+ '\s*,\s*'
+ '(' + number_re + ')' # end of the interval
+ '\s*'
+ '(\]|\))' # closing brecket
+ '\s*$'
)
match = re.search(interval_re, interval)
if match is None:
raise ValueError('Got an incorrect string representation of an interval: {!r}'. format(interval))
opening_brecket, begin, end, closing_brecket = match.groups()
self.begin, self.end = float(begin), float(end)
if self.begin >= self.end:
raise ValueError("Interval's begin shoud be smaller than it's end")
self.begin_included = opening_brecket == '['
self.end_included = closing_brecket == ']'
# It might have been batter to use number_re = '.*' and catch exeptions float() raises instead
def __repr__(self):
return 'Interval({!r})'.format(str(self))
def __str__(self):
opening_breacket = '[' if self.begin_included else '('
closing_breacket = ']' if self.end_included else ')'
return '{}{}, {}{}'.format(opening_breacket, self.begin, self.end, closing_breacket)
def __contains__(self, number):
if self.begin < number < self.end:
return True
if number == self.begin:
return self.begin_included
if number == self.end:
return self.end_included
Upvotes: 5
Reputation: 123473
You can't change Python's existing syntax rules (without changing the whole language), but you can get usably close to what you want:
class Interval(object):
def __init__(self, left_bracket, a, b, right_bracket):
if len(left_bracket) !=1 or left_bracket not in '[(':
raise ValueError(
'Unknown left bracket character: {!r}'.format(left_bracket))
if len(right_bracket) !=1 or right_bracket not in '])':
raise ValueError(
'Unknown right bracket character: {!r}'.format(right_bracket))
if a < b:
self.lower, self.upper = a, b
else:
self.lower, self.upper = b, a
self.left_bracket, self.right_bracket = left_bracket, right_bracket
if left_bracket == '[':
if right_bracket == ']':
self._contains = (
lambda self, val: self.lower <= val <= self.upper)
else:
self._contains = (
lambda self, val: self.lower <= val < self.upper)
else:
if right_bracket == ']':
self._contains = (
lambda self, val: self.lower < val <= self.upper)
else:
self._contains = (
lambda self, val: self.lower < val < self.upper)
__contains__ = lambda self, val: self._contains(self, val)
def __str__(self):
return '{}{}, {}{}'.format(self.left_bracket, self.lower, self.upper,
self.right_bracket)
def __repr__(self):
return '{}({!r}, {}, {}, {!r})'.format(self.__class__.__name__,
self.left_bracket, self.lower, self.upper, self.right_bracket)
if __name__ == '__main__':
interval1 = Interval('[', 1, 3, ']') # closed interval
interval2 = Interval('[', 1, 3, ')') # half-open interval
print('{} in {}? {}'.format(3, interval1, 3 in interval1))
print('{} in {}? {}'.format(3, interval2, 3 in interval2))
Output:
3 in [1, 3]? True
3 in [1, 3)? False
Note: The a
and b
arguments can be any type that can be compared.
Upvotes: 1
Reputation: 30151
You cannot make this exact syntax work. But you could do something like this by overriding the relevant comparison methods:
a <= Interval() < b
This whole expression could then return a new Interval
object that includes everything greater than or equal to a and strictly less than b. Interval()
by itself could be interpreted as the fully open interval from negative to positive infinity (i.e. the unbounded interval of all real numbers), and Interval() < b
by itself could refer to an interval bounded from above but not from below.
NumPy uses a similar technique for array comparison operations (where A < B means "return an array of ones and zeros that correspond to whether or not each element of A is less than the respective element of B").
Upvotes: 1