diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab8d47b..626f551 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: - id: name-tests-test # verifies that test files are named correctly. args: [--pytest-test-first] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 # ruff version + rev: v0.4.7 # ruff version hooks: - id: ruff # run the linter - id: ruff-format # run the formatter diff --git a/docs/source/pages/API_reference/generators/permutation_generator.rst b/docs/source/pages/API_reference/generators/generate.rst similarity index 63% rename from docs/source/pages/API_reference/generators/permutation_generator.rst rename to docs/source/pages/API_reference/generators/generate.rst index 5554732..01df830 100644 --- a/docs/source/pages/API_reference/generators/permutation_generator.rst +++ b/docs/source/pages/API_reference/generators/generate.rst @@ -1,15 +1,17 @@ -Permutation Generator +Generate ===================== To use the generator of permutation, import it as .. code-block:: python - from symmetria import permutation_generator + import symmetria + ... + permutations = symmetria.generate(algorithm="lexicographic", degree=3) The API of the method is given as following: .. automodule:: symmetria - :members: permutation_generator + :members: generate :exclude-members: Cycle, CycleDecomposition, Permutation \ No newline at end of file diff --git a/docs/source/pages/API_reference/generators/index.rst b/docs/source/pages/API_reference/generators/index.rst index b665b2a..761827c 100644 --- a/docs/source/pages/API_reference/generators/index.rst +++ b/docs/source/pages/API_reference/generators/index.rst @@ -23,4 +23,4 @@ A list of implemented algorithms to generate permutations: :maxdepth: 1 :hidden: - permutation_generator + generate diff --git a/symmetria/__init__.py b/symmetria/__init__.py index fa52061..850f145 100644 --- a/symmetria/__init__.py +++ b/symmetria/__init__.py @@ -1,12 +1,12 @@ import sys from symmetria.elements.cycle import Cycle -from symmetria.generators.api import permutation_generator +from symmetria.generators.api import generate from symmetria.elements.permutation import Permutation from symmetria.elements.cycle_decomposition import CycleDecomposition __version__ = "0.0.5" -__all__ = ["__version__", "permutation_generator", "Permutation", "Cycle", "CycleDecomposition"] +__all__ = ["__version__", "generate", "Permutation", "Cycle", "CycleDecomposition"] def _log_version() -> None: diff --git a/symmetria/generators/_algorithms.py b/symmetria/generators/_algorithms.py new file mode 100644 index 0000000..b089737 --- /dev/null +++ b/symmetria/generators/_algorithms.py @@ -0,0 +1,38 @@ +from typing import Generator + +import symmetria.elements.permutation + + +def _lexicographic_generator(degree: int) -> Generator["Permutation", None, None]: + """Private method to generate all the permutations of degree `degree` in lexicographic order. + + The algorithm is described as follows: + - Step 1: Consider the identity permutation in the given degree. + - Step 2: Find the largest index k such that permutation[k] < permutation[k + 1]. + - Step 3: If no k exists, then it is permutation is the last permutation. END. + - Step 4: Find the largest index j greater than k such that permutation[k] < permutation[j]. + - Step 5: Swap the value of permutation[k] with that of permutation[j]. + - Step 6: Reverse the sequence from permutation[k + 1] up to and including the final element permutation[degree]. + - Step 7: Go to Step 2. + """ + # step 1 + permutation = list(range(1, degree + 1)) + + while True: + yield symmetria.Permutation(*permutation) + + # step 2 + k = next((i for i in range(degree - 2, -1, -1) if permutation[i] < permutation[i + 1]), -1) + + # step 3 + if k == -1: + return None + + # step 4 + j = next(i for i in range(degree - 1, k, -1) if permutation[k] < permutation[i]) + + # step 5 + permutation[k], permutation[j] = permutation[j], permutation[k] + + # step 6 + permutation[k + 1 :] = reversed(permutation[k + 1 :]) diff --git a/symmetria/generators/api.py b/symmetria/generators/api.py index 41cea52..11b1dfc 100644 --- a/symmetria/generators/api.py +++ b/symmetria/generators/api.py @@ -1,13 +1,15 @@ from typing import List, Generator -__all__ = ["permutation_generator"] +from symmetria.generators._algorithms import _lexicographic_generator + +__all__ = ["generate"] _SUPPORTED_ALGORITHM: List[str] = [ "lexicographic", ] -def permutation_generator(degree: int, algorithm: str = "lexicographic") -> Generator["Permutation", None, None]: +def generate(degree: int, algorithm: str = "lexicographic") -> Generator["Permutation", None, None]: """Generate all the permutations of the degree based on the chosen algorithm. The method generates all the permutations of the given degree using the specified algorithm. @@ -22,17 +24,18 @@ def permutation_generator(degree: int, algorithm: str = "lexicographic") -> Gene :raises ValueError: If the algorithm is not supported or the degree is invalid. - # Activate once we have the lexicographic algorithm - #:examples: - # >>> from symmetria import permutation_generator - # ... - # >>> permutations = permutation_generator(degree=3, algorithm="lexicographic") - # >>> for perm in permutations: - # ... print(perm) - # Permutation(1, 2, 3) - # Permutation(1, 3, 2) - # Permutation(2, 1, 3) + :examples: + >>> import symmetria ... + >>> permutations = symmetria.generate(degree=3, algorithm="lexicographic") + >>> for permutation in permutations: + ... permutation + Permutation(1, 2, 3) + Permutation(1, 3, 2) + Permutation(2, 1, 3) + Permutation(2, 3, 1) + Permutation(3, 1, 2) + Permutation(3, 2, 1) """ _check_algorithm_parameter(value=algorithm) _check_degree_parameter(value=degree) @@ -66,4 +69,5 @@ def _check_degree_parameter(value: int) -> None: def _relevant_generator(algorithm: str, degree: int) -> Generator["Permutation", None, None]: """Private method to pick the correct algorithm for generating permutations.""" - raise NotImplementedError(f"To be implemented using {algorithm} and {degree}.") + if algorithm == "lexicographic": + return _lexicographic_generator(degree=degree) diff --git a/tests/tests_generators/test_api.py b/tests/tests_generators/test_api.py index 75ac130..3f8c164 100644 --- a/tests/tests_generators/test_api.py +++ b/tests/tests_generators/test_api.py @@ -1,7 +1,11 @@ import pytest -from symmetria import permutation_generator -from tests.tests_generators.test_cases import TEST_PERMUTATION_GENERATOR_EXCPETIONS +from symmetria import generate +from tests.test_utils import _check_values +from tests.tests_generators.test_cases import ( + TEST_LEXICOGRAPHIC_GENERATOR, + TEST_PERMUTATION_GENERATOR_EXCPETIONS, +) @pytest.mark.parametrize( @@ -11,4 +15,17 @@ ) def test_permutation_generator_exceptions(algorithm, degree, error, msg) -> None: with pytest.raises(error, match=msg): - _ = permutation_generator(algorithm=algorithm, degree=degree) + _ = generate(algorithm=algorithm, degree=degree) + + +@pytest.mark.parametrize( + argnames="degree, expected_value", + argvalues=TEST_LEXICOGRAPHIC_GENERATOR, + ids=[f"permutation_generator(lexicographic, {d})" for d, _ in TEST_LEXICOGRAPHIC_GENERATOR], +) +def test_lexicographic_generator(degree, expected_value) -> None: + _check_values( + expression=f"permutation_generator('lexicographic', {degree})", + evaluation=list(generate(algorithm="lexicographic", degree=degree)), + expected=expected_value, + ) diff --git a/tests/tests_generators/test_cases.py b/tests/tests_generators/test_cases.py index 0768108..2a9a6d9 100644 --- a/tests/tests_generators/test_cases.py +++ b/tests/tests_generators/test_cases.py @@ -1,6 +1,51 @@ +from symmetria import Permutation + TEST_PERMUTATION_GENERATOR_EXCPETIONS = [ (13, 3, TypeError, "The parameter `algorithm` must be of type string"), ("test", 3, ValueError, "The given algorithm"), ("lexicographic", "test", TypeError, "The parameter `degree`"), ("lexicographic", 0, ValueError, "The parameter `degree` must be a non-zero positive integer"), ] +TEST_LEXICOGRAPHIC_GENERATOR = [ + (1, [Permutation(1)]), + ( + 3, + [ + Permutation(1, 2, 3), + Permutation(1, 3, 2), + Permutation(2, 1, 3), + Permutation(2, 3, 1), + Permutation(3, 1, 2), + Permutation(3, 2, 1), + ], + ), + ( + 4, + [ + Permutation(1, 2, 3, 4), + Permutation(1, 2, 4, 3), + Permutation(1, 3, 2, 4), + Permutation(1, 3, 4, 2), + Permutation(1, 4, 2, 3), + Permutation(1, 4, 3, 2), + Permutation(2, 1, 3, 4), + Permutation(2, 1, 4, 3), + Permutation(2, 3, 1, 4), + Permutation(2, 3, 4, 1), + Permutation(2, 4, 1, 3), + Permutation(2, 4, 3, 1), + Permutation(3, 1, 2, 4), + Permutation(3, 1, 4, 2), + Permutation(3, 2, 1, 4), + Permutation(3, 2, 4, 1), + Permutation(3, 4, 1, 2), + Permutation(3, 4, 2, 1), + Permutation(4, 1, 2, 3), + Permutation(4, 1, 3, 2), + Permutation(4, 2, 1, 3), + Permutation(4, 2, 3, 1), + Permutation(4, 3, 1, 2), + Permutation(4, 3, 2, 1), + ], + ), +]