From 11f82422ec9f29bbebdd060cd3dbed7d88f9b0e9 Mon Sep 17 00:00:00 2001 From: bleudev Date: Sun, 10 Nov 2024 00:26:54 +0300 Subject: [PATCH 1/5] Basic functional for USets. Added withrepr() decorator --- ufpy/__init__.py | 4 ++ ufpy/algebra/__init__.py | 1 + ufpy/algebra/sets.py | 105 +++++++++++++++++++++++++++++++++++++++ ufpy/utils.py | 24 ++++++++- 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 ufpy/algebra/__init__.py create mode 100644 ufpy/algebra/sets.py 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..c10760e --- /dev/null +++ b/ufpy/algebra/__init__.py @@ -0,0 +1 @@ +from ufpy.algebra.sets import * \ No newline at end of file diff --git a/ufpy/algebra/sets.py b/ufpy/algebra/sets.py new file mode 100644 index 0000000..53496ef --- /dev/null +++ b/ufpy/algebra/sets.py @@ -0,0 +1,105 @@ +from __future__ import annotations +from os import environ + +from ufpy.math_op import i_generator, r_generator +from ufpy.utils import withrepr + +__all__ = ( + 'USet', + 'U' +) + +from typing import Iterable, Iterator, Optional, TypeVar, overload + +T = TypeVar('T') +OT = TypeVar('OT') + +@i_generator +@r_generator +class USet[T]: + @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: + self.__set = set(iterable) if iterable else set(values) + self.__auto_update_U = auto_update_U + self.__update__() + + @property + def set(self) -> set[T]: + return self.__set + + @set.setter + def set(self, value: Iterable[T]): + self.__set = set(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: + U(iterable=_get_U() | self) + + # Convert to other types + def __repr__(self) -> str: + return f'u{self.set if self.set else '{}'}' + + def __str__(self) -> str: + return repr(self.set) if self.set else '{}' + + # Iteration + def __iter__(self) -> Iterator[T]: + return iter(self.__set) + + # Or + def __or__(self, other: Iterable[OT]) -> USet[T | OT]: + return USet(iterable=set(self) | set(other), auto_update_U=self.__auto_update_U) + + def __add__(self, other: Iterable[OT]) -> USet[T | OT]: + return self | other + + # Substract + def __sub__(self, other: Iterable[OT]) -> USet[T]: + return USet(iterable=set(self) - set(other), auto_update_U=self.__auto_update_U) + + def __div__(self, other: Iterable[OT]) -> USet[T]: + return self - other + + # And + def __and__(self, other: Iterable[OT]) -> USet: + return USet(iterable=set(self) & set(other), auto_update_U=self.__auto_update_U) + + def __mul__(self, other: Iterable[OT]) -> USet: + return self & other + + # Not + def __neg__(self) -> USet: + return _get_U() - self + +environ["ufpy_USet_U"] = "{}" + +@overload +def U(*values: T) -> None: ... +@overload +def U(*, iterable: Iterable[T]) -> None: ... + +@withrepr(lambda _: repr(_get_U())) +def U(*values: T, iterable: Optional[Iterable[T]] = None) -> None: + environ["ufpy_USet_U"] = str(_get_U() + (USet(iterable=iterable, auto_update_U=False) + if iterable else + USet(*values, auto_update_U=False))) + +def _convert_type(s: str): + if "'" in s or '"' in s: return s.replace("'", '').replace('"', '') + elif s.replace('-', '').isdigit(): return int(s) + else: return s + +def _get_U(): + s = environ["ufpy_USet_U"].replace('{', '').replace('}', '').replace('(', '').replace(')', '') + st = s.split(', ') + return USet(iterable=[x for i in st if (x := _convert_type(i))], auto_update_U=False) diff --git a/ufpy/utils.py b/ufpy/utils.py index 3d3a984..585892c 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -2,9 +2,12 @@ 'get_items_for_several_keys', 'set_items_for_several_keys', 'del_items_for_several_keys', + 'withrepr' ) -from typing import TypeVar +from ast import Call +from typing import Any, Callable, TypeVar +import functools from ufpy.typ import SupportsGet, SupportsSetItem, SupportsDelItem, AnyCollection @@ -31,3 +34,22 @@ 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 From 3f1c05e21fe732c79df5fb26107166ca256b026e Mon Sep 17 00:00:00 2001 From: bleudev <95937737+bleudev@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:46:14 +0300 Subject: [PATCH 2/5] Use `or` operator instead of `if self.set else` Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- ufpy/algebra/sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufpy/algebra/sets.py b/ufpy/algebra/sets.py index 53496ef..4722e23 100644 --- a/ufpy/algebra/sets.py +++ b/ufpy/algebra/sets.py @@ -47,7 +47,7 @@ def __update__(self): # Convert to other types def __repr__(self) -> str: - return f'u{self.set if self.set else '{}'}' + return f'u{self.set or '{}'}' def __str__(self) -> str: return repr(self.set) if self.set else '{}' From a69c0b07b26334f88830efb431b4d4cb20a18077 Mon Sep 17 00:00:00 2001 From: bleudev Date: Mon, 11 Nov 2024 22:33:40 +0300 Subject: [PATCH 3/5] Implication, bug fixes USet and fix (r/i)_generator() --- ufpy/algebra/sets.py | 108 ++++++++++++++++++++++++++----------------- ufpy/math_op.py | 62 ++++++++++++++----------- ufpy/utils.py | 6 ++- 3 files changed, 105 insertions(+), 71 deletions(-) diff --git a/ufpy/algebra/sets.py b/ufpy/algebra/sets.py index 4722e23..5485c1f 100644 --- a/ufpy/algebra/sets.py +++ b/ufpy/algebra/sets.py @@ -1,12 +1,10 @@ from __future__ import annotations -from os import environ from ufpy.math_op import i_generator, r_generator -from ufpy.utils import withrepr +from ufpy.utils import is_iterable __all__ = ( 'USet', - 'U' ) from typing import Iterable, Iterator, Optional, TypeVar, overload @@ -17,6 +15,8 @@ @i_generator @r_generator class USet[T]: + U: USet = None + @overload def __init__(self, *values: T) -> None: ... @overload @@ -27,79 +27,101 @@ def __init__(self, *, iterable: Iterable[T]) -> None: ... 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: - self.__set = set(iterable) if iterable else set(values) + if USet.U == 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 self.__set + return set(self.__set) @set.setter def set(self, value: Iterable[T]): - self.__set = set(value) + 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: - U(iterable=_get_U() | self) + USet.U |= self # Convert to other types def __repr__(self) -> str: - return f'u{self.set or '{}'}' - - def __str__(self) -> str: - return repr(self.set) if self.set else '{}' - + 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]) -> USet[T | OT]: + 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]) -> USet[T | OT]: - return self | other + def __add__(self, other: Iterable[OT] | OT) -> USet[T | OT]: + return self._or(other) # Substract - def __sub__(self, other: Iterable[OT]) -> USet[T]: + 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 __div__(self, other: Iterable[OT]) -> USet[T]: - return self - other + 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]) -> USet: + 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 __mul__(self, other: Iterable[OT]) -> USet: - return self & other + 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 __neg__(self) -> USet: - return _get_U() - self - -environ["ufpy_USet_U"] = "{}" - -@overload -def U(*values: T) -> None: ... -@overload -def U(*, iterable: Iterable[T]) -> None: ... + def _not(self, s: Optional[USet[OT]] = None) -> USet[OT]: + return (s or USet.U) - self -@withrepr(lambda _: repr(_get_U())) -def U(*values: T, iterable: Optional[Iterable[T]] = None) -> None: - environ["ufpy_USet_U"] = str(_get_U() + (USet(iterable=iterable, auto_update_U=False) - if iterable else - USet(*values, auto_update_U=False))) + 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 -def _convert_type(s: str): - if "'" in s or '"' in s: return s.replace("'", '').replace('"', '') - elif s.replace('-', '').isdigit(): return int(s) - else: return s -def _get_U(): - s = environ["ufpy_USet_U"].replace('{', '').replace('}', '').replace('(', '').replace(')', '') - st = s.split(', ') - return USet(iterable=[x for i in st if (x := _convert_type(i))], auto_update_U=False) +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..984fa66 100644 --- a/ufpy/math_op.py +++ b/ufpy/math_op.py @@ -8,62 +8,72 @@ T = TypeVar('T') +def check(t: Type[T], s: str, astr: str): + s2 = f'__{astr}{s.replace('__', '')}__' + return s in t.__dict__ and s2 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 585892c..92b93cc 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -5,8 +5,7 @@ 'withrepr' ) -from ast import Call -from typing import Any, Callable, TypeVar +from typing import Any, Callable, TypeVar, Iterable import functools from ufpy.typ import SupportsGet, SupportsSetItem, SupportsDelItem, AnyCollection @@ -53,3 +52,6 @@ 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) From f1bc370252461ea9d73248edc0111961ed385b1e Mon Sep 17 00:00:00 2001 From: bleudev Date: Mon, 11 Nov 2024 22:45:20 +0300 Subject: [PATCH 4/5] Fix sourcery suggestions --- ufpy/algebra/sets.py | 2 +- ufpy/math_op.py | 55 ++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/ufpy/algebra/sets.py b/ufpy/algebra/sets.py index 5485c1f..eb690c9 100644 --- a/ufpy/algebra/sets.py +++ b/ufpy/algebra/sets.py @@ -27,7 +27,7 @@ def __init__(self, *, iterable: Iterable[T]) -> None: ... 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 == None and auto_update_U: + 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))) diff --git a/ufpy/math_op.py b/ufpy/math_op.py index 984fa66..27ffd36 100644 --- a/ufpy/math_op.py +++ b/ufpy/math_op.py @@ -9,38 +9,37 @@ T = TypeVar('T') def check(t: Type[T], s: str, astr: str): - s2 = f'__{astr}{s.replace('__', '')}__' - return s in t.__dict__ and s2 not in t.__dict__ + return f'__{s}__' in t.__dict__ and f'__{astr}{s}__' not in t.__dict__ def i_generator(t: Type[T]) -> Type[T]: def check_i(s: str): return check(t, s, 'i') - if check_i('__add__'): + if check_i('add'): t.__iadd__ = t.__add__ - if check_i('__sub__'): + if check_i('sub'): t.__isub__ = t.__sub__ - if check_i('__mul__'): + if check_i('mul'): t.__imul__ = t.__mul__ - if check_i('__floordiv__'): + if check_i('floordiv'): t.__ifloordiv__ = t.__floordiv__ - if check_i('__div__'): + if check_i('div'): t.__idiv__ = t.__div__ - if check_i('__truediv__'): + if check_i('truediv'): t.__itruediv__ = t.__truediv__ - if check_i('__mod__'): + if check_i('mod'): t.__imod__ = t.__mod__ - if check_i('__pow__'): + if check_i('pow'): t.__ipow__ = t.__pow__ - if check_i('__lshift__'): + if check_i('lshift'): t.__ilshift__ = t.__lshift__ - if check_i('__rshift__'): + if check_i('rshift'): t.__irshift__ = t.__rshift__ - if check_i('__and__'): + if check_i('and'): t.__iand__ = t.__and__ - if check_i('__or__'): + if check_i('or'): t.__ior__ = t.__or__ - if check_i('__xor__'): + if check_i('xor'): t.__ixor__ = t.__xor__ return t @@ -49,31 +48,31 @@ def r_generator(t: Type[T]) -> Type[T]: def check_r(s: str): return check(t, s, 'r') - if check_r('__add__'): + if check_r('add'): t.__radd__ = t.__add__ - if check_r('__sub__'): + if check_r('sub'): t.__rsub__ = t.__sub__ - if check_r('__mul__'): + if check_r('mul'): t.__rmul__ = t.__mul__ - if check_r('__floordiv__'): + if check_r('floordiv'): t.__rfloordiv__ = t.__floordiv__ - if check_r('__div__'): + if check_r('div'): t.__rdiv__ = t.__div__ - if check_r('__truediv__'): + if check_r('truediv'): t.__rtruediv__ = t.__truediv__ - if check_r('__mod__'): + if check_r('mod'): t.__rmod__ = t.__mod__ - if check_r('__pow__'): + if check_r('pow'): t.__rpow__ = t.__pow__ - if check_r('__lshift__'): + if check_r('lshift'): t.__rlshift__ = t.__lshift__ - if check_r('__rshift__'): + if check_r('rshift'): t.__rrshift__ = t.__rshift__ - if check_r('__and__'): + if check_r('and'): t.__rand__ = t.__and__ - if check_r('__or__'): + if check_r('or'): t.__ror__ = t.__or__ - if check_r('__xor__'): + if check_r('xor'): t.__rxor__ = t.__xor__ return t From 891331e75da09bb2d1347afc99b2eb065f69ce5d Mon Sep 17 00:00:00 2001 From: bleudev Date: Tue, 12 Nov 2024 22:22:04 +0300 Subject: [PATCH 5/5] Base for intervals --- ufpy/algebra/__init__.py | 1 + ufpy/algebra/interval.py | 94 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 ufpy/algebra/interval.py diff --git a/ufpy/algebra/__init__.py b/ufpy/algebra/__init__.py index c10760e..fc94e21 100644 --- a/ufpy/algebra/__init__.py +++ b/ufpy/algebra/__init__.py @@ -1 +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