diff --git a/ufpy/__init__.py b/ufpy/__init__.py index bd6db52..04f1c43 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -40,3 +40,7 @@ def new_func(*args, **kwargs): # GitHub package __github_version__ = '0.1' from ufpy.github import * + +# Algebra package +__algebra_version = '0.1' +from ufpy.algebra import * diff --git a/ufpy/algebra/__init__.py b/ufpy/algebra/__init__.py new file mode 100644 index 0000000..fc94e21 --- /dev/null +++ b/ufpy/algebra/__init__.py @@ -0,0 +1,2 @@ +from ufpy.algebra.interval import * +from ufpy.algebra.sets import * \ No newline at end of file diff --git a/ufpy/algebra/interval.py b/ufpy/algebra/interval.py new file mode 100644 index 0000000..fbd015e --- /dev/null +++ b/ufpy/algebra/interval.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from ufpy.cmp import cmp_generator +from ufpy.typ.type_alias import NumberLiteral + +__all__ = ( + 'UInterval', + 'inf', +) + +from typing import Generic, Literal, Optional, TypeVar + + +@cmp_generator +class Inf: + def __init__(self, sign: Literal[-1, 0, 1]) -> None: + self.sign = sign + + def __neg__(self) -> Inf: + return Inf(-1) + + def __pos__(self) -> Inf: + return Inf(+1) + + def __invert__(self) -> Inf: + return Inf(+1 if self.sign == -1 else -1 if self.sign == 1 else 0) + + def __cmp__(self, other) -> int: + if isinstance(other, Inf) and other.sign < self.sign: + return 0 + return 1 + + def __repr__(self) -> str: + r = '' + if self.sign == 1: + r += '+' + elif self.sign == -1: + r += '-' + + return f'{r}∞' + +inf = Inf(0) + +IntervalNumber = NumberLiteral | list[NumberLiteral] | Inf + +class UInterval: + def __init__(self, start: IntervalNumber, end: IntervalNumber = +inf) -> None: + self.__start = start[0] if isinstance(start, list) else start + self.__end = end[0] if isinstance(end, list) else end + self.inc = [int(isinstance(start, list)), int(isinstance(end, list))] + + @property + def start(self) -> NumberLiteral | Inf: + return self.__start + + @start.setter + def start(self, value: IntervalNumber): + self.__start = value[0] if isinstance(value, list) else value + if isinstance(value, list): + self.inc[0] = 1 + else: + self.inc[0] = 0 + + @property + def end(self) -> NumberLiteral | Inf: + return self.__end + + @end.setter + def end(self, value: IntervalNumber): + self.__end = value[0] if isinstance(value, list) else value + if isinstance(value, list): + self.inc[1] = 1 + else: + self.inc[1] = 0 + + def __repr__(self) -> str: + fbracket = '[' if self.inc[0] else '(' + sbracket = ']' if self.inc[1] else ')' + return f'u{fbracket}{self.__start}; {self.__end}{sbracket}' + + def in_interval(self, x: NumberLiteral | Inf) -> bool: + b = True + + if self.inc[0]: + b &= x >= self.__start + else: + b &= x > self.__start + + if self.inc[1]: + b &= x <= self.__end + else: + b &= x < self.__end + + return b diff --git a/ufpy/algebra/sets.py b/ufpy/algebra/sets.py new file mode 100644 index 0000000..eb690c9 --- /dev/null +++ b/ufpy/algebra/sets.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +from ufpy.math_op import i_generator, r_generator +from ufpy.utils import is_iterable + +__all__ = ( + 'USet', +) + +from typing import Iterable, Iterator, Optional, TypeVar, overload + +T = TypeVar('T') +OT = TypeVar('OT') + +@i_generator +@r_generator +class USet[T]: + U: USet = None + + @overload + def __init__(self, *values: T) -> None: ... + @overload + def __init__(self, *values: T, auto_update_U: bool) -> None: ... + @overload + def __init__(self, *, iterable: Iterable[T]) -> None: ... + @overload + def __init__(self, *, iterable: Iterable[T], auto_update_U: bool) -> None: ... + + def __init__(self, *values: T, iterable: Optional[Iterable[T]] = None, auto_update_U: bool = True) -> None: + if USet.U is None and auto_update_U: + USet.U = _U() + + self.__set = list(sorted(set(iterable))) if iterable else list(sorted(set(values))) + self.__auto_update_U = auto_update_U + self.__update__() + + @property + def set(self) -> set[T]: + return set(self.__set) + + @set.setter + def set(self, value: Iterable[T]): + self.__set = list(value) + self.__update__() + + # When USet updates (not required, that new USet must be not same that old one) + def __update__(self): + if self.__auto_update_U: + USet.U |= self + + # Convert to other types + def __repr__(self) -> str: + return 'u{' + ', '.join([str(i) for i in self.__set]) + '}' + + # Iteration + def __iter__(self) -> Iterator[T]: + return iter(self.__set) + + # Or + def _or(self, other: Iterable[OT] | OT) -> USet[T | OT]: + if not is_iterable(other): + other = [other] + return USet(iterable=set(self) | set(other), auto_update_U=self.__auto_update_U) + + def __or__(self, other: Iterable[OT] | OT) -> USet[T | OT]: + return self._or(other) + + def __add__(self, other: Iterable[OT] | OT) -> USet[T | OT]: + return self._or(other) + + # Substract + def sub(self, other: Iterable[OT] | OT) -> USet[T]: + if not is_iterable(other): + other = [other] + return USet(iterable=set(self) - set(other), auto_update_U=self.__auto_update_U) + + def __sub__(self, other: Iterable[OT] | OT) -> USet[T]: + return self.sub(other) + + def __rsub__(self, other: Iterable[OT] | OT) -> USet[T]: + return USet(iterable=other if is_iterable(other) else [other]).sub(self) + + def __truediv__(self, other: Iterable[OT] | OT) -> USet[T]: + return self.sub(other) + + # And + def _and(self, other: Iterable[OT] | OT) -> USet: + if not is_iterable(other): + other = [other] + return USet(iterable=set(self) & set(other), auto_update_U=self.__auto_update_U) + + def __and__(self, other: Iterable[OT] | OT) -> USet: + return self._and(other) + + def __mul__(self, other: Iterable[OT] | OT) -> USet: + return self._and(other) + + # Not + def _not(self, s: Optional[USet[OT]] = None) -> USet[OT]: + return (s or USet.U) - self + + def __neg__(self) -> USet: + return self._not() + + # Implication + def implicate(self, other: Iterable[OT] | OT) -> USet: + if not is_iterable(other): + other = [other] + return USet(iterable=(-self + other), auto_update_U=self.__auto_update_U) + + def __gt__(self, other: Iterable[OT] | OT) -> USet: + return self.implicate(other) + + def __ge__(self, other: Iterable[OT] | OT) -> USet: + return self > other + + def __lt__(self, other: Iterable[OT] | OT) -> USet: + other2 = USet(*(other if is_iterable(other) else [other])) + return other2.implicate(self) + + def __le__(self, other: Iterable[OT] | OT) -> USet: + return self < other + + +class _U(USet[T]): + def __init__(self, *values: T) -> None: + super().__init__(*values, auto_update_U=False) diff --git a/ufpy/math_op.py b/ufpy/math_op.py index 32c9a81..27ffd36 100644 --- a/ufpy/math_op.py +++ b/ufpy/math_op.py @@ -8,62 +8,71 @@ T = TypeVar('T') +def check(t: Type[T], s: str, astr: str): + return f'__{s}__' in t.__dict__ and f'__{astr}{s}__' not in t.__dict__ + def i_generator(t: Type[T]) -> Type[T]: - if '__add__' in t.__dict__: + def check_i(s: str): + return check(t, s, 'i') + + if check_i('add'): t.__iadd__ = t.__add__ - if '__sub__' in t.__dict__: + if check_i('sub'): t.__isub__ = t.__sub__ - if '__mul__' in t.__dict__: + if check_i('mul'): t.__imul__ = t.__mul__ - if '__floordiv__' in t.__dict__: + if check_i('floordiv'): t.__ifloordiv__ = t.__floordiv__ - if '__div__' in t.__dict__: + if check_i('div'): t.__idiv__ = t.__div__ - if '__truediv__' in t.__dict__: + if check_i('truediv'): t.__itruediv__ = t.__truediv__ - if '__mod__' in t.__dict__: + if check_i('mod'): t.__imod__ = t.__mod__ - if '__pow__' in t.__dict__: + if check_i('pow'): t.__ipow__ = t.__pow__ - if '__lshift__' in t.__dict__: + if check_i('lshift'): t.__ilshift__ = t.__lshift__ - if '__rshift__' in t.__dict__: + if check_i('rshift'): t.__irshift__ = t.__rshift__ - if '__and__' in t.__dict__: + if check_i('and'): t.__iand__ = t.__and__ - if '__or__' in t.__dict__: + if check_i('or'): t.__ior__ = t.__or__ - if '__xor__' in t.__dict__: + if check_i('xor'): t.__ixor__ = t.__xor__ return t def r_generator(t: Type[T]) -> Type[T]: - if '__add__' in t.__dict__: + def check_r(s: str): + return check(t, s, 'r') + + if check_r('add'): t.__radd__ = t.__add__ - if '__sub__' in t.__dict__: + if check_r('sub'): t.__rsub__ = t.__sub__ - if '__mul__' in t.__dict__: + if check_r('mul'): t.__rmul__ = t.__mul__ - if '__floordiv__' in t.__dict__: + if check_r('floordiv'): t.__rfloordiv__ = t.__floordiv__ - if '__div__' in t.__dict__: + if check_r('div'): t.__rdiv__ = t.__div__ - if '__truediv__' in t.__dict__: + if check_r('truediv'): t.__rtruediv__ = t.__truediv__ - if '__mod__' in t.__dict__: + if check_r('mod'): t.__rmod__ = t.__mod__ - if '__pow__' in t.__dict__: + if check_r('pow'): t.__rpow__ = t.__pow__ - if '__lshift__' in t.__dict__: + if check_r('lshift'): t.__rlshift__ = t.__lshift__ - if '__rshift__' in t.__dict__: + if check_r('rshift'): t.__rrshift__ = t.__rshift__ - if '__and__' in t.__dict__: + if check_r('and'): t.__rand__ = t.__and__ - if '__or__' in t.__dict__: + if check_r('or'): t.__ror__ = t.__or__ - if '__xor__' in t.__dict__: + if check_r('xor'): t.__rxor__ = t.__xor__ return t diff --git a/ufpy/utils.py b/ufpy/utils.py index 3d3a984..92b93cc 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -2,9 +2,11 @@ 'get_items_for_several_keys', 'set_items_for_several_keys', 'del_items_for_several_keys', + 'withrepr' ) -from typing import TypeVar +from typing import Any, Callable, TypeVar, Iterable +import functools from ufpy.typ import SupportsGet, SupportsSetItem, SupportsDelItem, AnyCollection @@ -31,3 +33,25 @@ def del_items_for_several_keys(o: SupportsDelItem[KT, VT], keys: AnyCollection[K for k in keys: del res[k] return res + +# Useful decorators + +class __reprwrapper: + def __init__(self, repr, func): + self._repr = repr + self._func = func + functools.update_wrapper(self, func) + def __call__(self, *args, **kw): + return self._func(*args, **kw) + def __repr__(self): + return self._repr(self._func) + +T = TypeVar('T', bound=Callable) + +def withrepr(f: Callable[[T], str]): + def _wrap(func: T): + return __reprwrapper(f, func) + return _wrap + +def is_iterable(o: object) -> bool: + return isinstance(o, Iterable)