diff --git a/src/cpl_query/base/default_lambda.py b/src/cpl_query/base/default_lambda.py new file mode 100644 index 00000000..3d985ff2 --- /dev/null +++ b/src/cpl_query/base/default_lambda.py @@ -0,0 +1,2 @@ +def default_lambda(x: object): + return x diff --git a/src/cpl_query/base/queryable_abc.py b/src/cpl_query/base/queryable_abc.py index 976c9ab4..fa22ab83 100644 --- a/src/cpl_query/base/queryable_abc.py +++ b/src/cpl_query/base/queryable_abc.py @@ -155,6 +155,15 @@ class QueryableABC(ABC): """ pass + def group_by(self, _func: Callable = None) -> 'QueryableABC': + r"""Groups by func + + Returns + ------- + Grouped list[list[any]]: any + """ + pass + @abstractmethod def last(self) -> any: r"""Returns last element diff --git a/src/cpl_query/iterable/iterable.py b/src/cpl_query/iterable/iterable.py index c8e54573..6f7ce73f 100644 --- a/src/cpl_query/iterable/iterable.py +++ b/src/cpl_query/iterable/iterable.py @@ -16,59 +16,36 @@ class Iterable(IterableABC): IterableABC.__init__(self, t, values) def all(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda - result = self.where(_func) - return len(result) == len(self) + return self.where(_func).count() == self.count() def any(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda - result = self.where(_func) - return len(result) > 0 + return self.where(_func).count() > 0 def average(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None and not is_number(self.type): raise InvalidTypeException() - if _func is None: - _func = _default_lambda - - return float(self.sum(_func)) / float(self.count()) + return self.sum(_func) / self.count() def contains(self, _value: object) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _value is None: raise ArgumentNoneException(ExceptionArgument.value) return self.where(lambda x: x == _value).count() > 0 def count(self, _func: Callable = None) -> int: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: - return len(self) + return self.__len__() - return len(self.where(_func)) - - def distinct(self, _func: Callable = None) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) + return self.where(_func).__len__() + def distinct(self, _func: Callable = None) -> 'Iterable': if _func is None: _func = _default_lambda @@ -85,18 +62,12 @@ class Iterable(IterableABC): return result def element_at(self, _index: int) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _index is None: raise ArgumentNoneException(ExceptionArgument.index) return self[_index] def element_at_or_default(self, _index: int) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _index is None: raise ArgumentNoneException(ExceptionArgument.index) @@ -105,56 +76,52 @@ class Iterable(IterableABC): except IndexError: return None - def first(self: IterableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def first(self) -> any: if len(self) == 0: raise IndexOutOfRangeException() return self[0] - def first_or_default(self: IterableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def first_or_default(self) -> Optional[any]: if len(self) == 0: return None return self[0] - def last(self: IterableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) == 0: - raise IndexOutOfRangeException() - - return self[len(self) - 1] - - def last_or_default(self: IterableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) == 0: - return None - - return self[len(self) - 1] - - def for_each(self, _func: Callable = None): - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def group_by(self, _func: Callable = None) -> 'Iterable': if _func is None: _func = _default_lambda + groups = {} - for element in self: - _func(element) + for v in self: + value = _func(v) + if v not in groups: + groups[value] = Iterable(type(v)) + + groups[value].append(v) + + return Iterable(Iterable).extend(groups.values()) + + def last(self) -> any: + if len(self) == 0: + raise IndexOutOfRangeException() + + return self[len(self) - 1] + + def last_or_default(self) -> Optional[any]: + if len(self) == 0: + return None + + return self[len(self) - 1] + + def for_each(self, _func: Callable = None) -> 'Iterable': + if _func is not None: + for element in self: + _func(element) + + return self def max(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None and not is_number(self.type): raise InvalidTypeException() @@ -166,6 +133,7 @@ class Iterable(IterableABC): def median(self, _func=None) -> Union[int, float]: if _func is None: _func = _default_lambda + result = self.order_by(_func).select(_func).to_list() length = len(result) i = int(length / 2) @@ -176,9 +144,6 @@ class Iterable(IterableABC): ) def min(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None and not is_number(self.type): raise InvalidTypeException() @@ -188,9 +153,6 @@ class Iterable(IterableABC): return _func(min(self, key=_func)) def order_by(self, _func: Callable = None) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda @@ -198,22 +160,16 @@ class Iterable(IterableABC): return OrderedIterable(self.type, _func, sorted(self, key=_func)) def order_by_descending(self, _func: Callable = None) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda from cpl_query.iterable.ordered_iterable import OrderedIterable return OrderedIterable(self.type, _func, sorted(self, key=_func, reverse=True)) - def reverse(self: IterableABC) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) + def reverse(self) -> 'Iterable': + return Iterable().extend(reversed(self)) - return Iterable().extend(reversed(self.to_list())) - - def select(self, _func: Callable = None) -> IterableABC: + def select(self, _func: Callable = None) -> 'Iterable': if _func is None: _func = _default_lambda @@ -221,10 +177,7 @@ class Iterable(IterableABC): result.extend(_func(_o) for _o in self) return result - def select_many(self, _func: Callable = None) -> IterableABC: - if _func is None: - _func = _default_lambda - + def select_many(self, _func: Callable = None) -> 'Iterable': result = Iterable() # The line below is pain. I don't understand anything of it... # written on 09.11.2022 by Sven Heidemann @@ -233,10 +186,7 @@ class Iterable(IterableABC): result.extend(elements) return result - def single(self: IterableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def single(self) -> any: if len(self) > 1: raise Exception('Found more than one element') elif len(self) == 0: @@ -244,10 +194,7 @@ class Iterable(IterableABC): return self[0] - def single_or_default(self: IterableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def single_or_default(self) -> Optional[any]: if len(self) > 1: raise Exception('Index out of range') elif len(self) == 0: @@ -255,19 +202,13 @@ class Iterable(IterableABC): return self[0] - def skip(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def skip(self, _index: int) -> 'Iterable': if _index is None: raise ArgumentNoneException(ExceptionArgument.index) return Iterable(self.type, values=self[_index:]) - def skip_last(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def skip_last(self, _index: int) -> 'Iterable': if _index is None: raise ArgumentNoneException(ExceptionArgument.index) @@ -277,10 +218,20 @@ class Iterable(IterableABC): result.extend(self[:index]) return result - def take(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) + def sum(self, _func: Callable = None) -> Union[int, float, complex]: + if _func is None and not is_number(self.type): + raise InvalidTypeException() + if _func is None: + _func = _default_lambda + + result = 0 + for x in self: + result += _func(x) + + return result + + def take(self, _index: int) -> 'Iterable': if _index is None: raise ArgumentNoneException(ExceptionArgument.index) @@ -288,10 +239,7 @@ class Iterable(IterableABC): result.extend(self[:_index]) return result - def take_last(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def take_last(self, _index: int) -> 'Iterable': index = len(self) - _index if index >= len(self) or index < 0: @@ -301,22 +249,7 @@ class Iterable(IterableABC): result.extend(self[index:]) return result - def sum(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return sum([_func(x) for x in self]) - - def where(self, _func: Callable = None) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def where(self, _func: Callable = None) -> 'Iterable': if _func is None: raise ArgumentNoneException(ExceptionArgument.func) @@ -328,4 +261,4 @@ class Iterable(IterableABC): if _func(element): result.append(element) - return result \ No newline at end of file + return result diff --git a/unittests/unittests/application.py b/unittests/unittests/application.py index 0ba34799..c9547c15 100644 --- a/unittests/unittests/application.py +++ b/unittests/unittests/application.py @@ -18,6 +18,6 @@ class Application(ApplicationABC): def main(self): runner = unittest.TextTestRunner() - runner.run(CLITestSuite()) + # runner.run(CLITestSuite()) runner.run(QueryTestSuite()) - runner.run(TranslationTestSuite()) + # runner.run(TranslationTestSuite()) diff --git a/unittests/unittests_query/iterable_query_test_case.py b/unittests/unittests_query/iterable_query_test_case.py index 626a4905..c48fc9ea 100644 --- a/unittests/unittests_query/iterable_query_test_case.py +++ b/unittests/unittests_query/iterable_query_test_case.py @@ -5,6 +5,7 @@ from random import randint from cpl_core.utils import String from cpl_query.exceptions import InvalidTypeException, ArgumentNoneException from cpl_query.extension.list import List +from cpl_query.iterable import Iterable from unittests_query.models import User, Address @@ -102,8 +103,18 @@ class IterableQueryTestCase(unittest.TestCase): self.assertEqual(1, self._tests.count(lambda u: u == self._t_user)) def test_distinct(self): - res = self._tests.distinct(lambda u: u.address.nr).where(lambda u: u.address.nr == 5) - self.assertEqual(1, len(res)) + res = self._tests.select(lambda u: u.address.nr).where(lambda a: a == 5).distinct() + self.assertEqual(1, res.count()) + + addresses = [] + for u in self._tests: + if u.address.nr in addresses: + continue + + addresses.append(u.address.nr) + + res2 = self._tests.distinct(lambda x: x.address.nr).select(lambda x: x.address.nr) + self.assertEqual(addresses, res2) def test_element_at(self): index = randint(0, len(self._tests) - 1) @@ -168,6 +179,29 @@ class IterableQueryTestCase(unittest.TestCase): self.assertEqual(res[0], s_res) self.assertIsNone(sn_res) + def test_group_by(self): + def by_adr(u): + return u.address.nr + + t = self._tests.select(by_adr).group_by() + res = self._tests.group_by(by_adr) + self.assertTrue(isinstance(res.first_or_default(), Iterable)) + self.assertNotEqual(self._tests.count(), res.count()) + self.assertEqual(self._tests.distinct(by_adr).count(), res.count()) + + elements = List(int) + groups = {} + for x in range(0, 1000): + v = randint(1, 100) + if v not in groups: + groups[v] = [] + + groups[v].append(v) + elements.append(v) + + r1, r2 = list(groups.values()), elements.group_by() + self.assertEqual(r1, r2) + def test_for_each(self): users = [] self._tests.for_each(lambda user: (