Source code for data_morph.bounds.interval
"""Class for working with bounds."""
from numbers import Number
from typing import Iterable
from ._utils import _validate_2d
[docs]
class Interval:
"""
Class representing a range of numeric values.
Parameters
----------
bounds : Iterable[numbers.Number]
A 2-dimensional numeric iterable.
inclusive : bool, default ``False``
Whether the bounds include the endpoints. Default
is exclusive.
"""
def __init__(
self,
bounds: Iterable[Number],
inclusive: bool = False,
) -> None:
self._bounds = self._validate_bounds(bounds)
self._inclusive = inclusive
def __contains__(self, value: Number) -> bool:
"""
Add support for using the ``in`` operator to check whether
``value`` is in the interval.
Parameters
----------
value : numbers.Number
A numeric value.
Returns
-------
bool
Whether ``value`` is contained in the interval.
"""
if not isinstance(value, Number) or isinstance(value, bool) or value is None:
raise TypeError('This operation is only supported for numeric values.')
if self._inclusive:
return self._bounds[0] <= value <= self._bounds[1]
return self._bounds[0] < value < self._bounds[1]
def __eq__(self, other: 'Interval') -> bool:
"""
Check whether two :class:`.Interval` objects are equivalent.
Parameters
----------
other : Interval
A :class:`.Interval` object.
Returns
-------
bool
Whether the two :class:`.Interval` objects are equivalent.
"""
if not isinstance(other, Interval):
raise TypeError('Equality is only defined between Interval objects.')
return self._bounds == other._bounds and self._inclusive == other._inclusive
def __getitem__(self, index: int) -> Number:
"""
Add support for indexing into the bounds.
Parameters
----------
index : int
The index to access.
Returns
-------
numbers.Number
The value for the bounds at ``index``.
"""
return self._bounds[index]
def __iter__(self) -> Number:
"""
Iterate over the bounds.
Returns
-------
numbers.Number
The next value of the bounds.
"""
return iter(self._bounds)
def __repr__(self) -> str:
values = ', '.join(map(str, self._bounds))
if self._inclusive:
interval = f'[{values}]'
kind = 'inclusive'
else:
interval = f'({values})'
kind = 'exclusive'
return f'<Interval {kind} {interval}>'
def _validate_bounds(self, bounds: Iterable[Number]) -> Iterable[Number]:
"""
Validate the proposed bounds.
Parameters
----------
bounds : Iterable[numbers.Number]
An iterable of min/max bounds.
Returns
-------
Iterable[numbers.Number]
An iterable of min/max bounds.
"""
bounds = list(_validate_2d(bounds, 'bounds'))
if bounds[0] >= bounds[1]:
raise ValueError('Right bound must be strictly greater than left bound.')
return bounds
[docs]
def adjust_bounds(self, value: Number) -> None:
"""
Adjust bound range.
Parameters
----------
value : numbers.Number
The amount to change the range by (half will be applied to each end).
"""
if isinstance(value, bool) or not isinstance(value, Number) or value is None:
raise TypeError('value must be a numeric value')
if not value:
raise ValueError('value must be non-zero')
offset = value / 2
self._bounds[0] -= offset
self._bounds[1] += offset
[docs]
def clone(self) -> 'Interval':
"""
Clone this instance.
Returns
-------
Interval
A new :class:`.Interval` instance with the same bounds.
"""
return Interval(self._bounds[:], self._inclusive)
@property
def range(self) -> Number:
"""
Calculate the range (width) of the interval.
Returns
-------
numbers.Number
The range covered by the interval.
"""
return abs(self._bounds[1] - self._bounds[0])