Skip to content

Commit

Permalink
[FEATURE] Add lexicographic algorithm (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
VascoSch92 authored Jun 2, 2024
1 parent a07a74f commit 4f93f70
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion docs/source/pages/API_reference/generators/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ A list of implemented algorithms to generate permutations:
:maxdepth: 1
:hidden:

permutation_generator
generate
4 changes: 2 additions & 2 deletions symmetria/__init__.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
38 changes: 38 additions & 0 deletions symmetria/generators/_algorithms.py
Original file line number Diff line number Diff line change
@@ -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 :])
30 changes: 17 additions & 13 deletions symmetria/generators/api.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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)
23 changes: 20 additions & 3 deletions tests/tests_generators/test_api.py
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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,
)
45 changes: 45 additions & 0 deletions tests/tests_generators/test_cases.py
Original file line number Diff line number Diff line change
@@ -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),
],
),
]

0 comments on commit 4f93f70

Please sign in to comment.