Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/source/data-structures/order/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Contents
rev_rpo_cmp
rpo_cmp
shortlex_compare
wt_lenlex_cmp
wt_lex_cmp

Full API
--------
Expand All @@ -47,3 +49,7 @@ Full API
.. autofunction:: rpo_cmp

.. autofunction:: shortlex_compare

.. autofunction:: wt_lenlex_cmp

.. autofunction:: wt_lex_cmp
6 changes: 6 additions & 0 deletions src/libsemigroups_pybind11/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@
shortlex_compare,
side,
tril,
wt_lenlex_cmp as _wt_lenlex_cmp,
wt_lex_cmp as _wt_lex_cmp,
)
except ModuleNotFoundError as e:
raise ModuleNotFoundError(
Expand All @@ -129,6 +131,8 @@
lex_cmp = _wrap_cxx_free_fn(_lex_cmp)
rev_rpo_cmp = _wrap_cxx_free_fn(_rev_rpo_cmp)
rpo_cmp = _wrap_cxx_free_fn(_rpo_cmp)
wt_lenlex_cmp = _wrap_cxx_free_fn(_wt_lenlex_cmp)
wt_lex_cmp = _wrap_cxx_free_fn(_wt_lex_cmp)


__all__ = [
Expand Down Expand Up @@ -182,6 +186,8 @@
"shortlex_compare",
"side",
"tril",
"wt_lenlex_cmp",
"wt_lex_cmp",
# Submodules
"action",
"adapters",
Expand Down
152 changes: 152 additions & 0 deletions src/order.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

// C++ stl headers....
#include <string> // for string
#include <vector> // for vector

// libsemigroups....
#include <libsemigroups/order.hpp> // for *_cmp, Order
Expand Down Expand Up @@ -137,6 +138,157 @@ word contains a letter that does not belong to *alphabet*.
:rtype: bool
)pbdoc");

m.def(
"wt_lenlex_cmp",
[](std::vector<size_t> const& weights, Word const& x, Word const& y) {
return wt_lenlex_cmp(weights, x, y);
},
py::arg("weights"),
py::arg("x"),
py::arg("y"),
R"pbdoc(
:sig=(weights: list[int], x: str | list[int], y: str | list[int]) -> bool:
:only-document-once:
Compare two words using weighted len-lex ordering.

The *i*-th entry of *weights* is the weight assigned to generator *i*.
Words are first ordered by their total weight, then by length, and finally
lexicographically.

:param weights: the weight assigned to each generator.
:type weights: list[int]
:param x: the first word.
:type x: str | list[int]
:param y: the second word.
:type y: str | list[int]
:returns: Whether *x* is less than *y*.
:rtype: bool

:raises LibsemigroupsError: if a letter is not a valid index into *weights*.

.. doctest:: python

>>> from libsemigroups_pybind11 import wt_lenlex_cmp
>>> wt_lenlex_cmp([1, 2], [1], [0, 0])
True
)pbdoc");

m.def(
"wt_lenlex_cmp",
[](Alphabet<Word> const& alphabet,
std::vector<size_t> const& weights,
Word const& x,
Word const& y) { return wt_lenlex_cmp(alphabet, weights, x, y); },
py::arg("alphabet"),
py::arg("weights"),
py::arg("x"),
py::arg("y"),
R"pbdoc(
:sig=(alphabet: Alphabet, weights: list[int], x: str | list[int], y: str | list[int]) -> bool:
:only-document-once:
Compare two words using alphabet-aware weighted len-lex ordering.

Letters are mapped to their positions in *alphabet*, and the *i*-th entry of
*weights* is the weight assigned to the *i*-th letter of *alphabet*. Words are
first ordered by their total weight, then by length, and finally
lexicographically according to *alphabet*.

:param alphabet: the ordered alphabet containing the letters of both words.
:type alphabet: Alphabet
:param weights: the weight assigned to each letter of *alphabet*.
:type weights: list[int]
:param x: the first word.
:type x: str | list[int]
:param y: the second word.
:type y: str | list[int]
:returns: Whether *x* is less than *y*.
:rtype: bool

:raises LibsemigroupsError: if a letter does not belong to *alphabet*, or its
position in *alphabet* is not a valid index into *weights*.

.. doctest:: python

>>> from libsemigroups_pybind11 import Alphabet, wt_lenlex_cmp
>>> wt_lenlex_cmp(Alphabet("ba"), [1, 1], "b", "a")
True
)pbdoc");

m.def(
"wt_lex_cmp",
[](std::vector<size_t> const& weights, Word const& x, Word const& y) {
return wt_lex_cmp(weights, x, y);
},
py::arg("weights"),
py::arg("x"),
py::arg("y"),
R"pbdoc(
:sig=(weights: list[int], x: str | list[int], y: str | list[int]) -> bool:
:only-document-once:
Compare two words using weighted lexicographic ordering.

The *i*-th entry of *weights* is the weight assigned to generator *i*.
Words are first ordered by their total weight and then lexicographically.

:param weights: the weight assigned to each generator.
:type weights: list[int]
:param x: the first word.
:type x: str | list[int]
:param y: the second word.
:type y: str | list[int]
:returns: Whether *x* is less than *y*.
:rtype: bool

:raises LibsemigroupsError: if a letter is not a valid index into *weights*.

.. doctest:: python

>>> from libsemigroups_pybind11 import wt_lex_cmp
>>> wt_lex_cmp([1, 2], [1], [0, 0])
False
)pbdoc");

m.def(
"wt_lex_cmp",
[](Alphabet<Word> const& alphabet,
std::vector<size_t> const& weights,
Word const& x,
Word const& y) { return wt_lex_cmp(alphabet, weights, x, y); },
py::arg("alphabet"),
py::arg("weights"),
py::arg("x"),
py::arg("y"),
R"pbdoc(
:sig=(alphabet: Alphabet, weights: list[int], x: str | list[int], y: str | list[int]) -> bool:
:only-document-once:
Compare two words using alphabet-aware weighted lexicographic ordering.

Letters are mapped to their positions in *alphabet*, and the *i*-th entry of
*weights* is the weight assigned to the *i*-th letter of *alphabet*. Words are
first ordered by their total weight and then lexicographically according to
*alphabet*.

:param alphabet: the ordered alphabet containing the letters of both words.
:type alphabet: Alphabet
:param weights: the weight assigned to each letter of *alphabet*.
:type weights: list[int]
:param x: the first word.
:type x: str | list[int]
:param y: the second word.
:type y: str | list[int]
:returns: Whether *x* is less than *y*.
:rtype: bool

:raises LibsemigroupsError: if a letter does not belong to *alphabet*, or its
position in *alphabet* is not a valid index into *weights*.

.. doctest:: python

>>> from libsemigroups_pybind11 import Alphabet, wt_lex_cmp
>>> wt_lex_cmp(Alphabet("ba"), [1, 1], "b", "a")
True
)pbdoc");

bind_compare<Word>(
m,
"rpo_cmp",
Expand Down
40 changes: 40 additions & 0 deletions tests/test_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
rev_rpo_cmp,
rpo_cmp,
shortlex_compare,
wt_lenlex_cmp,
wt_lex_cmp,
)


Expand Down Expand Up @@ -73,3 +75,41 @@ def test_deprecated_comparisons(old_compare, new_compare):
with pytest.deprecated_call():
result = old_compare("ab", "ba")
assert result == new_compare("ab", "ba")


@pytest.mark.parametrize("compare", [wt_lenlex_cmp, wt_lex_cmp])
def test_weighted_comparisons_for_integer_words(compare):
"""Check weighted comparisons and validation for integer words."""
weights = [2, 1, 6]

assert compare(weights, [0, 1], [2])
assert not compare(weights, [2], [0, 1])

with pytest.raises(LibsemigroupsError):
compare(weights, [0], [3])


def test_weighted_comparisons_use_different_tie_breakers():
"""Check the length and lexicographic tie breakers differ."""
weights = [1, 2]

assert wt_lenlex_cmp(weights, [1], [0, 0])
assert not wt_lex_cmp(weights, [1], [0, 0])


@pytest.mark.parametrize("compare", [wt_lenlex_cmp, wt_lex_cmp])
def test_weighted_comparisons_with_alphabet(compare):
"""Check weighted comparisons over an explicitly ordered alphabet."""
alphabet = Alphabet("ba")

assert compare(alphabet, [10, 1], "a", "b")
assert compare(alphabet, [1, 1], "b", "a")

word_alphabet = Alphabet([1, 0])
assert compare(word_alphabet, [1, 1], [1], [0])

with pytest.raises(LibsemigroupsError):
compare(alphabet, [1, 1], "c", "a")

with pytest.raises(LibsemigroupsError):
compare(alphabet, [1], "a", "b")
Loading