diff --git a/CHANGELOG.md b/CHANGELOG.md index c3fb0d4..8df05be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### New Features +- Implemented `none` function to complement the `any` and `all` boolean function +- Retrofitted `any` and `all` with a parameter so as to provide the functionality of `exists` and `for_all` respectively - Added `first_or_none`, a function to match `head_or_none` - Added run_test.sh script - Added [parametrize](https://pypi.org/project/parametrize/) for parameterized unit tests diff --git a/README.md b/README.md index f6c0e32..c7f9f87 100644 --- a/README.md +++ b/README.md @@ -388,11 +388,12 @@ complete documentation reference | `count(func)` | Returns count of elements in sequence where `func(element)` is True | action | | `empty()` | Returns `True` if the sequence has zero length | action | | `non_empty()` | Returns `True` if sequence has non-zero length | action | -| `all()` | Returns `True` if all elements in sequence are truthy | action | +| `all(func=None)` | Returns `True` if `func(element)` is `True` for all elements in sequence or, if `func` not specified, all elements are truthy | action | | `exists(func)` | Returns `True` if `func(element)` for any element in the sequence is `True` | action | | `for_all(func)` | Returns `True` if `func(element)` is `True` for all elements in the sequence | action | | `find(func)` | Returns the first element for which `func(element)` evaluates to `True` | action | -| `any()` | Returns `True` if any element in sequence is truthy | action | +| `any(func=None)` | Returns `True` if `func(element)` for any element in sequence is `True` or, if `func` not specified, any element is truthy | action | +| `none(func=None)` | Returns `True` if `func(element)` for all elements in sequence is `False` or, if `func` not specified, all elements are falsy | action | | `max()` | Returns maximal element in sequence | action | | `min()` | Returns minimal element in sequence | action | | `max_by(func)` | Returns element with maximal value `func(element)` | action | diff --git a/functional/pipeline.py b/functional/pipeline.py index a6ef89f..3df5b70 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -875,9 +875,17 @@ def non_empty(self) -> bool: """ return self.size() != 0 - def any(self) -> bool: + def any(self, func: Callable[[_T_co], Any] | None = None) -> bool: """ - Returns True if any element in the sequence has truth value True + Returns True if func on an element in the sequence evaluates to True, + or if func is not specified or None, True is returned if any element + has truth value of True. + + >>> seq([1, 2, 3, 4]).any(lambda x: x == 2) + True + + >>> seq([1, 2, 3, 4]).any(lambda x: x < 0) + False >>> seq([True, False]).any() True @@ -885,13 +893,26 @@ def any(self) -> bool: >>> seq([False, False]).any() False - :return: True if any element is True + :param func: function to check elements + :return: True if func on any element returns True, + or if func is not specified, True if any element is True """ - return any(self) + if func is None: + return any(self) + else: + return any(func(element) for element in self) - def all(self) -> bool: + def all(self, func: Callable[[_T_co], Any] | None = None) -> bool: """ - Returns True if the truth value of all items in the sequence true. + Returns True if func on all elements in sequence evaluates to True, + or if func is not specified or None, True is returned if all elements + have a truth value of True. + + >>> seq([1, 2, 3]).all(lambda x: x > 0) + True + + >>> seq([1, 2, -1]).all(lambda x: x > 0) + False >>> seq([True, True]).all() True @@ -899,9 +920,38 @@ def all(self) -> bool: >>> seq([True, False]).all() False - :return: True if all items truth value evaluates to True + :param func: function to check elements + :return: True if func on all elements returns True, + or if func is not specified, True if all elements are True + """ + if func is None: + return all(self) + else: + return all(func(element) for element in self) + + def none(self, func: Callable[[_T_co], Any] | None = None) -> bool: + """ + Returns True if func on all elements in sequence evaluates to False, + or if func is not specified or None, True is returned if all elements + have a truth value of False. + + >>> seq([-1, -2, -3]).none(lambda x: x > 0) + True + + >>> seq([1, 2, -1]).none(lambda x: x > 0) + False + + >>> seq([False, False]).none() + True + + >>> seq([True, False]).none() + False + + :param func: function to check elements + :return: True if func on all elements returns False, + or if func is not specified, True if all elements are False """ - return all(self) + return not self.any(func) def exists(self, func: Callable[[_T_co], Any]) -> bool: """ diff --git a/functional/test/test_functional.py b/functional/test/test_functional.py index f35e7d2..a42b2ef 100644 --- a/functional/test/test_functional.py +++ b/functional/test/test_functional.py @@ -530,15 +530,51 @@ def test_slice(self): self.assertIteratorEqual(result, [2, 3]) self.assert_type(result) - def test_any(self): - l = [True, False] - self.assertTrue(self.seq(l).any()) - - def test_all(self): - l = [True, False] - self.assertFalse(self.seq(l).all()) - l = [True, True] - self.assertTrue(self.seq(l).all()) + @parametrize( + "sequence, no_function", + [ + (["aaa", "BBB", "ccc"], False), + ([True, False], True), + ], + ) + def test_any(self, sequence, no_function): + if no_function: + self.assertTrue(self.seq(sequence).any()) + else: + self.assertTrue(self.seq(sequence).any(str.islower)) + self.assertTrue(self.seq(sequence).any(str.isupper)) + self.assertFalse(self.seq(sequence).any(lambda s: "d" in s)) + + @parametrize( + "sequence, expected, no_function", + [ + ([True, False], False, True), + ([True, True], True, True), + (["aaa", "bbb", "ccc"], True, False), + ], + ) + def test_all(self, sequence, expected, no_function): + if no_function: + self.assertEqual(expected, self.seq(sequence).all()) + else: + self.assertTrue(self.seq(sequence).all(str.islower)) + self.assertFalse(self.seq(sequence).all(str.isupper)) + + @parametrize( + "sequence, expected, no_function", + [ + ([False, False], True, True), + ([True, False], False, True), + ([True, True], False, True), + (["aaa", "bbb", "ccc"], True, False), + ], + ) + def test_none(self, sequence, expected, no_function): + if no_function: + self.assertEqual(expected, self.seq(sequence).none()) + else: + self.assertTrue(self.seq(sequence).none(str.isupper)) + self.assertFalse(self.seq(sequence).none(str.islower)) def test_enumerate(self): l = [2, 3, 4] diff --git a/functional/test/test_type.py b/functional/test/test_type.py index c9d0644..500404f 100644 --- a/functional/test/test_type.py +++ b/functional/test/test_type.py @@ -108,6 +108,8 @@ def type_checking() -> None: t_all: bool = seq([True, True]).all() + t_none: bool = seq([False, False]).none() + t_exists: bool = seq([1, 2, 3, 4]).exists(lambda x: x == 2) t_for_all: bool = seq([1, 2, 3]).for_all(lambda x: x > 0)