diff --git a/examples/algebra/fibonacci_sequence.md b/examples/algebra/fibonacci_sequence.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/algebra/progressions.md b/examples/algebra/progressions.md new file mode 100644 index 0000000..055dfc0 --- /dev/null +++ b/examples/algebra/progressions.md @@ -0,0 +1,10 @@ +# Progressions (FunctionSequence, Arithmetic, Geometric) + +## Introduction + +Progressions classes in `ufpy.algebra` package was made for simplification of working with math sequences/progressions. +For example, with arithmetic and geometric progressions. + +## `FunctionSequence` class + + diff --git a/ufpy/__init__.py b/ufpy/__init__.py index bd6db52..ebea5ac 100644 --- a/ufpy/__init__.py +++ b/ufpy/__init__.py @@ -33,10 +33,13 @@ def new_func(*args, **kwargs): from ufpy.ustl import * UStack = __deprecated("UStack", Stack, '0.2', '0.5') -# Path package __path_version__ = '0.1' from ufpy.path import * # 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..7896d3b --- /dev/null +++ b/ufpy/algebra/__init__.py @@ -0,0 +1,4 @@ +from ufpy.algebra.arithmetic import * +from ufpy.algebra.fibonacci import * +from ufpy.algebra.func_sequence import * +from ufpy.algebra.geometric import * diff --git a/ufpy/algebra/arithmetic.py b/ufpy/algebra/arithmetic.py new file mode 100644 index 0000000..09609bc --- /dev/null +++ b/ufpy/algebra/arithmetic.py @@ -0,0 +1,24 @@ +__all__ = ( + 'Arithmetic', +) + +from typing import TypeVar + +from ufpy.algebra.func_sequence import FunctionSequence + +VT = TypeVar('VT', int, float, int | float) +DT = TypeVar('DT', int, float, int | float) + +class Arithmetic(FunctionSequence[VT, DT], name_of_elements='a'): + def k_func(self, a_m: VT, a_n: VT, m: int, n: int) -> DT: + return (a_n - a_m) / (n - m) + + @property + def d(self) -> DT: + return self.k + + def __init__(self, *, d: DT = None, **kwargs): + def f(n, k, am, m): + return am + k * (n - m) + + super().__init__(f, d, **kwargs) diff --git a/ufpy/algebra/fibonacci.py b/ufpy/algebra/fibonacci.py new file mode 100644 index 0000000..ab6f1b5 --- /dev/null +++ b/ufpy/algebra/fibonacci.py @@ -0,0 +1,69 @@ +__all__ = ( + 'Fibonacci', +) + +from typing import overload, Any + +from ufpy.utils import mul + + +class Fibonacci: + # 1, 1, 2, 3, 5, 8, ... + + @overload + def __call__(self, n: int) -> int: ... + @overload + def __call__(self, start: int, end: int) -> list[int]: ... + def __call__(self, start_or_end: int, end: int = None): + # Format input parameters + if not end: + end = start_or_end + start = start_or_end + + # Generate fibonacci sequence from 1st to `end`th item + r = [] + + for i in range(end): + if i <= 1: + r.append(1) + else: + r.append(r[i - 1] + r[i - 2]) + + # Return result + if start == end: + return r[-1] + else: + return r[start - 1:] + + def __getitem__(self, n: int | slice): + if isinstance(n, slice): + start, end = x if (x := n.start) else 1, n.stop + return self(start, end) + return self(n) + + def __check_for_list(self, l_or_v: list | Any): + if isinstance(l_or_v, list): + return l_or_v + return [l_or_v] + + @overload + def s(self, n: int) -> int: ... + @overload + def s(self, start: int, end: int) -> int: ... + def s(self, start_or_n: int, end: int = None) -> int: + if end: + start = start_or_n + else: + start, end = 1, start_or_n + return sum(self.__check_for_list(self(start, end))) + + @overload + def p(self, n: int) -> int: ... + @overload + def p(self, start: int, end: int) -> int: ... + def p(self, start_or_n: int, end: int = None) -> int: + if end: + start = start_or_n + else: + start, end = 1, start_or_n + return mul(self.__check_for_list(self(start, end))) diff --git a/ufpy/algebra/func_sequence.py b/ufpy/algebra/func_sequence.py new file mode 100644 index 0000000..2a279de --- /dev/null +++ b/ufpy/algebra/func_sequence.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +__all__ = ( + 'FunctionSequence', +) + +from typing import Callable, TypeVar, Generic, Literal, overload, Any + +from ufpy.utils import mul +from ufpy.cmp import cmp_generator + +VT = TypeVar('VT', int, float, int | float) +KT = TypeVar('KT', int, float, int | float) + +VT2 = TypeVar('VT2', int, float, int | float) +KT2 = TypeVar('KT2', int, float, int | float) + +@cmp_generator +class FunctionSequence(Generic[VT, KT]): + def __resolve_item(self, kwarg: tuple[str, VT]) -> tuple[int, VT] | None: + name, value = kwarg + name = name.lower() + + if name.startswith(self.__name_of_elements) and (x := name.replace(self.__name_of_elements, '')).isdigit(): + if int(x) <= 0: + raise Exception("'n' must be bigger that 0!") + return int(x), value + return None + + def __resolve_sum_and_composition(self, kwarg: tuple[str, VT]) -> tuple[Literal['s', 'p'], int, VT] | None: + name, value = kwarg + name = name.lower() + + if name.startswith('s') and (x := name.replace('s', '')).isdigit(): + return 's', int(x), value + if name.startswith('p') and (y := name.replace('p', '')): + return 'p', int(y), value + return None + + def __process_float(self, x: float): + return int(x) if x.is_integer() else x + + def k_func(self, a_m: VT, a_n: VT, m: int, n: int) -> KT: + ... # return k + + def __init_subclass__(cls, name_of_elements: str, **kwargs): + super().__init_subclass__(**kwargs) + cls.__name_of_elements = name_of_elements + + def __init__(self, f: Callable[[int, KT, VT, int], VT], k: KT = None, **kwargs: VT): + self.ref_func = f + self.f = lambda n: self.ref_func(n, self.k, self.first, 1) + + el = [x for i in kwargs.items() if (x := self.__resolve_item(i))] + + if k: + self.k = self.__process_float(k) + if len(el) >= 1: + i1, v1 = el[0] + self.first = self.__process_float(self.ref_func(1, self.k, v1, i1)) + return + if len(el) >= 2: + (i1, v1), (i2, v2) = el[0], el[1] + self.k = self.__process_float(self.k_func(v1, v2, i1, i2)) + self.first = self.__process_float(self.ref_func(1, self.k, v1, i1)) + return + raise Exception("__init__ can't get k") + + @overload + def __call__(self, n: int) -> VT: ... + @overload + def __call__(self, start: int, end: int) -> list[VT]: ... + def __call__(self, start: int, end: int | None = None): + start1 = start + if not end: + end = start + + r = [] + + for i in range(start1, end+1): + r.append(self.__process_float(self.f(i))) + + return r if len(r) > 1 else r[0] + + def __getitem__(self, n: int | slice): + if isinstance(n, slice): + start, end = x if (x := n.start) else 1, n.stop + return self(start, end) + return self(n) + + def __cmp__(self, other: FunctionSequence[VT2, KT2]): + return self.k - other.k + + def __check_for_list(self, l_or_v: list | Any): + if isinstance(l_or_v, list): + return l_or_v + return [l_or_v] + + @overload + def s(self, n: int) -> VT: ... + @overload + def s(self, start: int, end: int) -> VT: ... + def s(self, start: int, end: int | None = None) -> VT: + if end is None: + start, end = 1, start + return sum(self.__check_for_list(self(start, end))) + + @overload + def p(self, n: int) -> VT: ... + @overload + def p(self, start: int, end: int) -> VT: ... + def p(self, start: int, end: int | None = None) -> VT: + if end is None: + start, end = 1, start + return mul(self.__check_for_list(self(start, end))) diff --git a/ufpy/algebra/geometric.py b/ufpy/algebra/geometric.py new file mode 100644 index 0000000..c96f79a --- /dev/null +++ b/ufpy/algebra/geometric.py @@ -0,0 +1,24 @@ +__all__ = ( + 'Geometric', +) + +from typing import TypeVar + +from ufpy.algebra.func_sequence import FunctionSequence + +VT = TypeVar('VT', int, float, int | float) +QT = TypeVar('QT', int, float, int | float) + +class Geometric(FunctionSequence[VT, QT], name_of_elements='b'): + def k_func(self, a_m: VT, a_n: VT, m: int, n: int) -> QT: + return (a_n / a_m) ** (1 / (n - m)) + + @property + def q(self) -> QT: + return self.k + + def __init__(self, *, q: QT = None, **kwargs): + def f(n, k, am, m): + return am * (k**(n-m)) + + super().__init__(f, q, **kwargs) diff --git a/ufpy/utils.py b/ufpy/utils.py index 3d3a984..ad54ddd 100644 --- a/ufpy/utils.py +++ b/ufpy/utils.py @@ -1,10 +1,13 @@ __all__ = ( + 'mul', 'get_items_for_several_keys', 'set_items_for_several_keys', 'del_items_for_several_keys', ) -from typing import TypeVar +from functools import reduce +from operator import mul as op_mul +from typing import TypeVar, Iterable from ufpy.typ import SupportsGet, SupportsSetItem, SupportsDelItem, AnyCollection @@ -13,6 +16,9 @@ DV = TypeVar('DV') +def mul(iterable: Iterable[VT]) -> VT: + return reduce(op_mul, iterable, 1) + def get_items_for_several_keys(o: SupportsGet[KT, VT], keys: AnyCollection[KT], default: DV = None) -> list[VT | DV]: return [o.get(k, default) for k in keys]