From 66670e0ce2e3c2b50703263cc1de267a0c6cbc6d Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Fri, 31 May 2024 21:13:36 +0200 Subject: [PATCH] [FEATURE] Implement `exceedences` method (#77) --- CHANGELOG.md | 2 + .../pages/API_reference/elements/index.rst | 5 + symmetria/elements/_interface.py | 11 +- symmetria/elements/cycle_decomposition.py | 29 +++ symmetria/elements/permutation.py | 31 +++ tests/test_factory.py | 222 +++++++----------- tests/tests_cycle_decomposition/test_cases.py | 7 + .../test_generic_methods.py | 12 + tests/tests_permutation/test_cases.py | 7 + tests/tests_permutation/test_constructors.py | 24 +- .../tests_permutation/test_generic_methods.py | 12 + 11 files changed, 200 insertions(+), 162 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 259e494..092f8ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ FEATURE: - `symmetria.CyclePermutation`: add `ascents` method - `symmetria.Permutation`: add `descents` method - `symmetria.CyclePermutation`: add `descents` method +- `symmetria.Permutation`: add `exceedances` method +- `symmetria.CyclePermutation`: add `exceedances` method ## \[0.0.4\] - 2024-05-28 diff --git a/docs/source/pages/API_reference/elements/index.rst b/docs/source/pages/API_reference/elements/index.rst index c8280a9..0c33c65 100644 --- a/docs/source/pages/API_reference/elements/index.rst +++ b/docs/source/pages/API_reference/elements/index.rst @@ -65,6 +65,11 @@ Here, **P** denotes the class ``Permutation``, **C** the class ``Cycle``, and ** - ✅ - ❌ - ✅ + * - ``exceedances`` + - Return the positions of the permutation exceedances + - ✅ + - ❌ + - ✅ * - ``inverse`` - Compute the inverse of the permutation - ✅ diff --git a/symmetria/elements/_interface.py b/symmetria/elements/_interface.py index e57766a..4045f5b 100644 --- a/symmetria/elements/_interface.py +++ b/symmetria/elements/_interface.py @@ -45,12 +45,6 @@ def __str__(self) -> str: """Implement method `str()`.""" raise NotImplementedError - # TODO: decide if we want to implement this method also for the classes Cycle and CycleDecomposition - # @staticmethod - # @abstractmethod - # def from_dict(permutation: Dict) -> "Element": - # raise NotImplementedError - @abstractmethod def cycle_decomposition(self) -> "CycleDecomposition": """Return the cycle decomposition of the element.""" @@ -117,9 +111,10 @@ def map(self) -> Dict[int, int]: """Return a dictionary representing the map defined the element.""" raise NotImplementedError - def name(self) -> str: + @staticmethod + def name() -> str: """Shortcut for the class name. Used for the tests.""" - return self.__class__.__name__ + return __class__.__name__ @abstractmethod def orbit(self, item: Any) -> List[Any]: diff --git a/symmetria/elements/cycle_decomposition.py b/symmetria/elements/cycle_decomposition.py index 3c52bc0..50f58e7 100644 --- a/symmetria/elements/cycle_decomposition.py +++ b/symmetria/elements/cycle_decomposition.py @@ -526,6 +526,35 @@ def equivalent(self, other: Any) -> bool: return symmetria.elements.permutation.Permutation.from_cycle_decomposition(self) == other return False + def exceedances(self, weakly: bool = False) -> List[int]: + r"""Return the exceedances of the cycle decomposition. + + Recall that an exceedance of a permutation :math:`\sigma \in S_n`, where :math:`n \in \mathbb{N}`, is any + position :math:`i \in \{ 1, ..., n\}` where :math:`\sigma(i) > i`. An exceedance is called weakly if + :math:`\sigma(i) \geq i`. + + :param weakly: `True` to return the weakly exceedances of the cycle decomposition. Default `False`. + :type weakly: bool + + :return: The exceedances of the cycle decomposition. + :rtype: List[int] + + :example: + >>> from symmetria import Cycle, CycleDecomposition + ... + >>> CycleDecomposition(Cycle(1, 2), Cycle(3)).exceedances() + [1] + >>> CycleDecomposition(Cycle(1, 2), Cycle(3)).exceedances(weakly=True) + [1, 3] + >>> CycleDecomposition(Cycle(2, 3), Cycle(4, 5, 1)).exceedances() + [1, 2, 4] + >>> CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)).exceedances() + [] + >>> CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)).exceedances(weakly=True) + [1, 2, 3] + """ + return symmetria.Permutation.from_cycle_decomposition(self).exceedances(weakly=weakly) + def inverse(self) -> "CycleDecomposition": r"""Return the inverse of the cycle decomposition. diff --git a/symmetria/elements/permutation.py b/symmetria/elements/permutation.py index 20546a4..0704d51 100644 --- a/symmetria/elements/permutation.py +++ b/symmetria/elements/permutation.py @@ -515,6 +515,37 @@ def equivalent(self, other: Any) -> bool: return self == Permutation.from_cycle_decomposition(other) return False + def exceedances(self, weakly: bool = False) -> List[int]: + r"""Return the exceedances of the permutation. + + Recall that an exceedance of a permutation :math:`\sigma \in S_n`, where :math:`n \in \mathbb{N}`, is any + position :math:`i \in \{ 1, ..., n\}` where :math:`\sigma(i) > i`. An exceedance is called weakly if + :math:`\sigma(i) \geq i`. + + :param weakly: `True` to return the weakly exceedances of the permutation. Default `False`. + :type weakly: bool + co + :return: The exceedances of the permutation. + :rtype: List[int] + + :example: + >>> from symmetria import Permutation + ... + >>> Permutation(1, 2, 3).exceedances() + [] + >>> Permutation(1, 2, 3).exceedances(weakly=True) + [1, 2, 3] + >>> Permutation(4, 3, 2, 1).exceedances() + [1, 2] + >>> Permutation(3, 4, 5, 2, 1, 6, 7).exceedances() + [1, 2, 3] + >>> Permutation(3, 4, 5, 2, 1, 6, 7).exceedances(weakly=True) + [1, 2, 3, 6, 7] + """ + if weakly: + return [i for i, p in enumerate(self.image, 1) if p >= i] + return [i for i, p in enumerate(self.image, 1) if p > i] + @classmethod def from_cycle(cls, cycle: "Cycle") -> "Permutation": """Return a permutation from a cycle. diff --git a/tests/test_factory.py b/tests/test_factory.py index 556c10d..ae4a6f0 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -1,43 +1,13 @@ -from typing import ( - Any, - Set, - Dict, - List, - Type, - Tuple, -) +from typing import Any, Set, Dict, List, Type, Tuple import pytest from symmetria import CycleDecomposition -########################## -# CONSTRUCTOR VALIDATORS # -########################## - - -def validate_from_dict(class_: Any, constructor: Dict, expected_value: Any) -> None: - if class_.from_dict(constructor) != expected_value: - raise ValueError( - f"The expression `{class_.name()}.from_dict({constructor}))` must evaluate {expected_value}, " - f"but got {class_.from_dict(constructor)}." - ) - -def validate_from_cycle(class_: Any, constructor: Dict, expected_value: Any) -> None: - if class_.from_cycle(constructor) != expected_value: - raise ValueError( - f"The expression `{class_.name()}.from_cycle({constructor}))` must evaluate {expected_value}, " - f"but got {class_.from_cycle(constructor)}." - ) - - -def validate_from_cycle_decomposition(class_: Any, constructor: Dict, expected_value: Any) -> None: - if class_.from_cycle_decomposition(constructor) != expected_value: - raise ValueError( - f"The expression `{class_.name()}.from_cycle_decomposition({constructor}))` must evaluate {expected_value}, " - f"but got {class_.from_cycle_decomposition(constructor)}." - ) +def _check_values(expression: str, evaluation: Any, expected: Any) -> None: + if evaluation != expected: + raise ValueError(f"The expression `{expression}` must evaluate {expected}, but got {evaluation}.") ############################## @@ -46,138 +16,99 @@ def validate_from_cycle_decomposition(class_: Any, constructor: Dict, expected_v def validate_ascents(item: Any, expected_value: List[int]) -> None: - if item.ascents() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.ascents()` must evaluate {expected_value}, but got {item.ascents()}." - ) + _check_values(expression=f"{item.rep()}.ascents()", evaluation=item.ascents(), expected=expected_value) def validate_cycle_decomposition(item: Any, expected_value: CycleDecomposition) -> None: - if item.cycle_decomposition() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.cycle_notation()` must evaluate {expected_value}, " - f"but got {item.cycle_decomposition()}." - ) + _check_values( + expression=f"{item.rep()}.cycle_notation()", evaluation=item.cycle_decomposition(), expected=expected_value + ) def validate_cycle_type(item: Any, expected_value: Tuple[int]) -> None: - if item.cycle_type() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.cycle_type()` must evaluate {expected_value}, but got {item.cycle_type()}." - ) + _check_values(expression=f"{item.rep()}.cycle_type()", evaluation=item.cycle_type(), expected=expected_value) def validate_cycle_notation(item: Any, expected_value: str) -> None: - if item.cycle_notation() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.cycle_notation()` must evaluate {expected_value}, " - f"but got {item.cycle_notation()}." - ) + _check_values(expression=f"{item.rep()}.cycle_notation()", evaluation=item.cycle_notation(), expected=expected_value) def validate_descents(item: Any, expected_value: List[int]) -> None: - if item.descents() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.descents()` must evaluate {expected_value}, but got {item.descents()}." - ) + _check_values(expression=f"{item.rep()}.descents()", evaluation=item.descents(), expected=expected_value) + + +def validate_exceedances(item: Any, weakly: bool, expected_value: List[int]) -> None: + _check_values( + expression=f"{item.rep()}.exceedances(weakly={weakly})", + evaluation=item.exceedances(weakly=weakly), + expected=expected_value, + ) def validate_inverse(item: Any, expected_value: Any) -> None: - if item.inverse() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.inverse()` must evaluate {expected_value}, but got {item.inverse()}." - ) + _check_values(expression=f"{item.rep()}.inverse()", evaluation=item.inverse(), expected=expected_value) def validate_inversions(item: Any, expected_value: List[Tuple[int, int]]) -> None: - if item.inversions() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.inversions()` must evaluate {expected_value}, but got {item.inversions()}." - ) + _check_values(expression=f"{item.rep()}.inversions()", evaluation=item.inversions(), expected=expected_value) def validate_is_conjugate(item: Any, other: Any, expected_value: bool) -> None: - if item.is_conjugate(other) is not expected_value: - raise ValueError( - f"The expression `{item.rep()}.is_conjugate({other.rep()})` must evaluate {expected_value}, " - f"but got {item.is_conjugate(other)}." - ) + _check_values( + expression=f"{item.rep()}.is_conjugate({other.rep()})", + evaluation=item.is_conjugate(other), + expected=expected_value, + ) def validate_is_derangement(item: Any, expected_value: bool) -> None: - if item.is_derangement() is not expected_value: - raise ValueError( - f"The expression `{item.rep()}.is_derangement()` must evaluate {expected_value}, " - f"but got {item.is_derangement()}." - ) + _check_values(expression=f"{item.rep()}.is_derangement()", evaluation=item.is_derangement(), expected=expected_value) def validate_is_even(item: Any, expected_value: bool) -> None: - if item.is_even() is not expected_value: - raise ValueError( - f"The expression `{item.rep()}.is_even()` must evaluate {expected_value}, but got {item.is_even()}." - ) + _check_values(expression=f"{item.rep()}.is_even()", evaluation=item.is_even(), expected=expected_value) def validate_is_odd(item: Any, expected_value: bool) -> None: - if item.is_odd() is not expected_value: - raise ValueError( - f"The expression `{item.rep()}.is_odd()` must evaluate {expected_value}, but got {item.is_odd()}." - ) + _check_values(expression=f"{item.rep()}.is_odd()", evaluation=item.is_odd(), expected=expected_value) def validate_is_regular(item: Any, expected_value: bool) -> None: - if item.is_regular() is not expected_value: - raise ValueError( - f"The expression `{item.rep()}.is_regular()` must evaluate {expected_value}, but got {item.is_regular()}." - ) + _check_values(expression=f"{item.rep()}.is_regular()", evaluation=item.is_regular(), expected=expected_value) def validate_equivalent(lhs: Any, rhs: Any, expected_value: bool) -> None: - if lhs.equivalent(other=rhs) != expected_value: - raise ValueError( - f"The expression `{lhs.rep()}.equivalent({rhs.rep()})` must evaluate {expected_value}, " - f"but got {lhs.equivalent(other=rhs)}." - ) + _check_values( + expression=f"{lhs.rep()}.equivalent({rhs.__repr__()})", + evaluation=lhs.equivalent(other=rhs), + expected=expected_value, + ) def validate_domain(item: Any, expected_value: bool) -> None: - if item.domain != expected_value: - raise ValueError( - f"The expression `{item.rep()}.domain()` must evaluate {expected_value}, but got {item.domain}." - ) + _check_values(expression=f"{item.rep()}.domain()", evaluation=item.domain, expected=expected_value) def validate_map(item: Any, expected_value: Dict[int, int]) -> None: - if item.map != expected_value: - raise ValueError(f"The expression `{item.rep()}.map()` must evaluate {expected_value}, but got {item.map}.") + _check_values(expression=f"{item.rep()}.map()", evaluation=item.map, expected=expected_value) def validate_orbit(element: Any, item: Any, expected_value: int) -> None: - if element.orbit(item=item) != expected_value: - raise ValueError( - f"The expression `{element.rep()}.orbit({item})` must evaluate {expected_value}, " - f"but got {element.orbit(item=item)}." - ) + _check_values( + expression=f"{element.rep()}.orbit({item})", evaluation=element.orbit(item=item), expected=expected_value + ) def validate_order(item: Any, expected_value: int) -> None: - if item.order() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.order()` must evaluate {expected_value}, but got {item.order()}." - ) + _check_values(expression=f"{item.rep()}.order()", evaluation=item.order(), expected=expected_value) def validate_sgn(item: Any, expected_value: int) -> None: - if item.sgn() != expected_value: - raise ValueError(f"The expression `{item.rep()}.sgn()` must evaluate {expected_value}, but got {item.sgn()}.") + _check_values(expression=f"{item.rep()}.sgn()", evaluation=item.sgn(), expected=expected_value) def validate_support(item: Any, expected_value: Set[int]) -> None: - if item.support() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.support()` must evaluate {expected_value}, but got {item.support()}." - ) + _check_values(expression=f"{item.rep()}.support()", evaluation=item.support(), expected=expected_value) ############################ @@ -186,16 +117,11 @@ def validate_support(item: Any, expected_value: Set[int]) -> None: def validate_bool(item: Any, expected_value: bool) -> None: - if bool(item) != expected_value: - raise ValueError(f"The expression `bool({item.rep()})` must evaluate {expected_value}, but got {bool(item)}.") + _check_values(expression=f"bool({item.rep()})", evaluation=bool(item), expected=expected_value) def validate_call(item: Any, call_on: Any, expected_value: Any) -> None: - if item(call_on) != expected_value: - raise ValueError( - f"The expression `{item.rep()}({call_on})` must evaluate {expected_value}, but got " - f"{item(call_on).__repr__()}." - ) + _check_values(expression=f"{item.rep()}({call_on})", evaluation=item(call_on), expected=expected_value) def validate_call_error(item: Any, call_on: Any, error: Type[Exception], msg: str) -> None: @@ -204,30 +130,23 @@ def validate_call_error(item: Any, call_on: Any, error: Type[Exception], msg: st def validate_eq(lhs: Any, rhs: Any, expected_value: bool) -> None: - if (lhs == rhs) != expected_value: - raise ValueError( - f"The expression `{lhs.rep()}=={rhs.rep()}` must evaluate {expected_value}, but got {lhs == rhs}." - ) + _check_values(expression=f"{lhs.__repr__()}=={rhs.__repr__()}", evaluation=(lhs == rhs), expected=expected_value) def validate_getitem(item: Any, idx: int, expected_value: int) -> None: - if item[idx] != expected_value: - raise ValueError(f"The expression {item.rep()}[{idx}] must evaluate {expected_value}, but got {item[idx]}.") + _check_values(expression=f"{item.rep()}[{idx}]", evaluation=item[idx], expected=expected_value) def validate_int(item: Any, expected_value: int) -> None: - if int(item) != expected_value: - raise ValueError(f"The expression `int({item.rep()})` must evaluate {expected_value}, but got {int(item)}.") + _check_values(expression=f"int({item.rep()})", evaluation=int(item), expected=expected_value) def validate_len(item: Any, expected_value: int) -> None: - if len(item) != expected_value: - raise ValueError(f"The expression `len({item.rep()})` must evaluate {expected_value}, but got {len(item)}.") + _check_values(expression=f"len({item.rep()})", evaluation=len(item), expected=expected_value) def validate_mul(lhs: Any, rhs: Any, expected_value: Any) -> None: - if (lhs * rhs) != expected_value: - raise ValueError(f"The expression `{lhs.rep()}*{rhs.rep()}` must evaluate {expected_value}, but got {lhs*rhs}.") + _check_values(expression=f"{lhs.rep()}*{rhs.rep()}", evaluation=lhs * rhs, expected=expected_value) def validate_mul_error(lhs: Any, rhs: Any, error: Type[Exception], msg: str) -> None: @@ -236,10 +155,7 @@ def validate_mul_error(lhs: Any, rhs: Any, error: Type[Exception], msg: str) -> def validate_pow(item: Any, power: int, expected_value: Any) -> None: - if item**power != expected_value: - raise ValueError( - f"The expression `{item.rep()} ** {power}` must evaluate {expected_value}, but got {item ** power}." - ) + _check_values(expression=f"{item.rep()} ** {power}", evaluation=item**power, expected=expected_value) def validate_pow_error(item: Any, power: Any, error: Type[Exception], msg: str) -> None: @@ -248,14 +164,34 @@ def validate_pow_error(item: Any, power: Any, error: Type[Exception], msg: str) def validate_repr(item: Any, expected_value: str) -> None: - if item.__repr__() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.__repr__()` must evaluate {expected_value}, but got {item.__repr__()}." - ) + _check_values(expression=f"{item.name}.__repr__()", evaluation=item.__repr__(), expected=expected_value) def validate_str(item: Any, expected_value: str) -> None: - if item.__str__() != expected_value: - raise ValueError( - f"The expression `{item.rep()}.__str__()` must evaluate {expected_value}, but got {item.__str__()}." - ) + _check_values(expression=f"{item.rep()}.__str__()", evaluation=item.__str__(), expected=expected_value) + + +@pytest.mark.parametrize( + argnames="expression, evaluation, expected", + argvalues=[ + ("test_1", 1, 1), + ("test_2", "a", "a"), + ("test_3", [], []), + ("test_4", [1, 2, 3], [1, 2, 3]), + ], +) +def test_check_value(expression, evaluation, expected) -> None: + _check_values(expression=expression, evaluation=evaluation, expected=expected) + + +@pytest.mark.parametrize( + argnames="expression, evaluation, expected, message", + argvalues=[ + ("test_1", 1, 0, "The expression `test_1` must evaluate 0, but got 1."), + ("test_2", "a", "b", "The expression `test_2` must evaluate b, but got a."), + ("test_3", [], {}, r"The expression `test_3` must evaluate \{\}, but got \[\]."), + ], +) +def test_check_value_raise_exception(expression, evaluation, expected, message) -> None: + with pytest.raises(ValueError, match=message): + _check_values(expression=expression, evaluation=evaluation, expected=expected) diff --git a/tests/tests_cycle_decomposition/test_cases.py b/tests/tests_cycle_decomposition/test_cases.py index 7aec142..ae270c2 100644 --- a/tests/tests_cycle_decomposition/test_cases.py +++ b/tests/tests_cycle_decomposition/test_cases.py @@ -52,6 +52,13 @@ (CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)), []), (CycleDecomposition(Cycle(2, 3), Cycle(4, 5, 1)), [1, 2, 4]), ] +TEST_EXCEEDANCES = [ + (CycleDecomposition(Cycle(1, 2), Cycle(3)), False, [1]), + (CycleDecomposition(Cycle(1, 2), Cycle(3)), True, [1, 3]), + (CycleDecomposition(Cycle(2, 3), Cycle(4, 5, 1)), False, [1, 2, 4]), + (CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)), False, []), + (CycleDecomposition(Cycle(1), Cycle(2), Cycle(3)), True, [1, 2, 3]), +] TEST_INVERSE = [ (CycleDecomposition(Cycle(1, 2, 3)), CycleDecomposition(Cycle(3, 2, 1))), (CycleDecomposition(Cycle(1, 2), Cycle(3, 4)), CycleDecomposition(Cycle(2, 1), Cycle(4, 3))), diff --git a/tests/tests_cycle_decomposition/test_generic_methods.py b/tests/tests_cycle_decomposition/test_generic_methods.py index 68c9cea..d268ce7 100644 --- a/tests/tests_cycle_decomposition/test_generic_methods.py +++ b/tests/tests_cycle_decomposition/test_generic_methods.py @@ -15,6 +15,7 @@ validate_equivalent, validate_inversions, validate_is_regular, + validate_exceedances, validate_is_conjugate, validate_cycle_notation, validate_is_derangement, @@ -35,6 +36,7 @@ TEST_EQUIVALENT, TEST_INVERSIONS, TEST_IS_REGULAR, + TEST_EXCEEDANCES, TEST_IS_CONJUGATE, TEST_CYCLE_NOTATION, TEST_IS_DERANGEMENT, @@ -93,6 +95,16 @@ def test_descents(cycle_decomposition, expected_value) -> None: validate_descents(item=cycle_decomposition, expected_value=expected_value) +@pytest.mark.parametrize( + argnames="cycle_decomposition, weakly, expected_value", + argvalues=TEST_EXCEEDANCES, + ids=[f"{p.__repr__()}.exceedences(weakly={w})={i}" for p, w, i in TEST_EXCEEDANCES], +) +def test_exceedences(cycle_decomposition, weakly, expected_value) -> None: + """Tests for the method `exceedences()`.""" + validate_exceedances(item=cycle_decomposition, weakly=weakly, expected_value=expected_value) + + @pytest.mark.parametrize( argnames="cycle_decomposition, expected_value", argvalues=TEST_INVERSE, diff --git a/tests/tests_permutation/test_cases.py b/tests/tests_permutation/test_cases.py index 48df35f..24627d7 100644 --- a/tests/tests_permutation/test_cases.py +++ b/tests/tests_permutation/test_cases.py @@ -97,6 +97,13 @@ (Permutation(1, 2, 3), 123, False), (Permutation(1, 3, 2, 4), "hello-world", False), ] +TEST_EXCEEDANCES = [ + (Permutation(1, 2, 3), False, []), + (Permutation(1, 2, 3), True, [1, 2, 3]), + (Permutation(4, 3, 2, 1), False, [1, 2]), + (Permutation(3, 4, 5, 2, 1, 6, 7), False, [1, 2, 3]), + (Permutation(3, 4, 5, 2, 1, 6, 7), True, [1, 2, 3, 6, 7]), +] TEST_IMAGE = [ (Permutation(1, 2, 3), (1, 2, 3)), (Permutation(1, 2, 3), (1, 2, 3)), diff --git a/tests/tests_permutation/test_constructors.py b/tests/tests_permutation/test_constructors.py index 04544fb..6f9e281 100644 --- a/tests/tests_permutation/test_constructors.py +++ b/tests/tests_permutation/test_constructors.py @@ -1,11 +1,7 @@ import pytest from symmetria import Permutation -from tests.test_factory import ( - validate_from_dict, - validate_from_cycle, - validate_from_cycle_decomposition, -) +from tests.test_factory import _check_values from tests.tests_permutation.test_cases import ( TEST_CONSTRUCTOR, TEST_CONSTRUCTOR_ERROR, @@ -43,7 +39,11 @@ def test_constructor_error(permutation, error, msg) -> None: ) def test_constructor_from_dict(dict_permutation, expected_value) -> None: """Tests for the constructor method `from_dict()`.""" - validate_from_dict(class_=Permutation, constructor=dict_permutation, expected_value=expected_value) + _check_values( + expression=f"Permutation.from_dict({dict_permutation}))", + evaluation=Permutation.from_dict(dict_permutation), + expected=expected_value, + ) @pytest.mark.parametrize( @@ -53,7 +53,9 @@ def test_constructor_from_dict(dict_permutation, expected_value) -> None: ) def test_constructor_from_cycle(cycle, expected_value) -> None: """Tests for the constructor method `from_cycle()`.""" - validate_from_cycle(class_=Permutation, constructor=cycle, expected_value=expected_value) + _check_values( + expression=f"Permutation.from_cycle({cycle}", evaluation=Permutation.from_cycle(cycle), expected=expected_value + ) @pytest.mark.parametrize( @@ -63,8 +65,8 @@ def test_constructor_from_cycle(cycle, expected_value) -> None: ) def test_constructor_from_cycle_decomposition(cycle_decomposition, expected_value) -> None: """Tests for the constructor method `from_cycle_decomposition()`.""" - validate_from_cycle_decomposition( - class_=Permutation, - constructor=cycle_decomposition, - expected_value=expected_value, + _check_values( + expression=f"Permutation.from_cycle_decomposition({cycle_decomposition})", + evaluation=Permutation.from_cycle_decomposition(cycle_decomposition), + expected=expected_value, ) diff --git a/tests/tests_permutation/test_generic_methods.py b/tests/tests_permutation/test_generic_methods.py index 39c02b8..415f2a4 100644 --- a/tests/tests_permutation/test_generic_methods.py +++ b/tests/tests_permutation/test_generic_methods.py @@ -16,6 +16,7 @@ validate_equivalent, validate_inversions, validate_is_regular, + validate_exceedances, validate_is_conjugate, validate_cycle_notation, validate_is_derangement, @@ -38,6 +39,7 @@ TEST_EQUIVALENT, TEST_INVERSIONS, TEST_IS_REGULAR, + TEST_EXCEEDANCES, TEST_IS_CONJUGATE, TEST_CYCLE_NOTATION, TEST_IS_DERANGEMENT, @@ -117,6 +119,16 @@ def test_equivalent(lhs, rhs, expected_value) -> None: validate_equivalent(lhs=lhs, rhs=rhs, expected_value=expected_value) +@pytest.mark.parametrize( + argnames="permutation, weakly, expected_value", + argvalues=TEST_EXCEEDANCES, + ids=[f"{p}.exceedances(weakly={w})={i}" for p, w, i in TEST_EXCEEDANCES], +) +def test_exceedances(permutation, weakly, expected_value) -> None: + """Tests the method `exceedances()`.""" + validate_exceedances(item=permutation, weakly=weakly, expected_value=expected_value) + + @pytest.mark.parametrize( argnames="permutation, expected_value", argvalues=TEST_IMAGE,