Jon Warneke
Jon Warneke

Reputation: 337

Notation for intervals?

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

Answers (4)

rturquier
rturquier

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

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)

Possible implementation:

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

martineau
martineau

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

Kevin
Kevin

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

Related Questions