diff --git a/CHANGELOG.md b/CHANGELOG.md index b13f6ee..c35d1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ - fix hashing for custom classes #### Added -- add support for slicing in `Editops.__getitem__` +- add support for slicing in `Editops.__getitem__`/`Editops.__delitem__` ### [2.5.0] - 2022-08-14 #### Added diff --git a/extern/rapidfuzz-cpp b/extern/rapidfuzz-cpp index d937555..ac6ae2e 160000 --- a/extern/rapidfuzz-cpp +++ b/extern/rapidfuzz-cpp @@ -1 +1 @@ -Subproject commit d937555ad76a6f1ed853ab4b7102a7b22b6f0fcf +Subproject commit ac6ae2e61ecd3b6fc19ac7568c2db7ad8b100a47 diff --git a/src/rapidfuzz/cpp_common.pxd b/src/rapidfuzz/cpp_common.pxd index 752c9b0..1a8ca41 100644 --- a/src/rapidfuzz/cpp_common.pxd +++ b/src/rapidfuzz/cpp_common.pxd @@ -109,6 +109,7 @@ cdef extern from "rapidfuzz/details/types.hpp" namespace "rapidfuzz" nogil: RfEditops inverse() except + RfEditops remove_subsequence(const RfEditops& subsequence) except + RfEditops slice(int, int, int) except + + void remove_slice(int, int, int) except + int64_t get_src_len() void set_src_len(int64_t) int64_t get_dest_len() diff --git a/src/rapidfuzz/distance/__init__.pyi b/src/rapidfuzz/distance/__init__.pyi index f29f2b5..c33aa6c 100644 --- a/src/rapidfuzz/distance/__init__.pyi +++ b/src/rapidfuzz/distance/__init__.pyi @@ -1,4 +1,4 @@ -from typing import Tuple, List, Union, Any +from typing import Tuple, List, Union from . import ( Hamming as Hamming, @@ -50,7 +50,7 @@ class Editops: def copy(self) -> Editops: ... def inverse(self) -> Editops: ... def remove_subsequence(self, subsequence: Editops) -> Editops: ... - def apply(self, source_string: str | bytes, destination_string: str | bytes) -> str: ... + def apply(self, source_string: Union[str, bytes], destination_string: Union[str, bytes]) -> str: ... @property def src_len(self) -> int: ... @src_len.setter @@ -59,8 +59,8 @@ class Editops: def dest_len(self) -> int: ... @dest_len.setter def dest_len(self, value: int) -> None: ... - def __delitem__(self, item: int) -> None: ... - def __getitem__(self, key: int) -> Editop: ... + def __delitem__(self, item: Union[int, slice]) -> None: ... + def __getitem__(self, key: Union[int, slice]) -> Editop: ... def __repr__(self) -> str: ... class Opcode: @@ -91,7 +91,7 @@ class Opcodes: def __len__(self) -> int: ... def copy(self) -> Opcodes: ... def inverse(self) -> Opcodes: ... - def apply(self, source_string: str | bytes, destination_string: str | bytes) -> str: ... + def apply(self, source_string: Union[str, bytes], destination_string: Union[str, bytes]) -> str: ... @property def src_len(self) -> int: ... @src_len.setter diff --git a/src/rapidfuzz/distance/_initialize_cpp.pyx b/src/rapidfuzz/distance/_initialize_cpp.pyx index c6e7e53..f1d9932 100644 --- a/src/rapidfuzz/distance/_initialize_cpp.pyx +++ b/src/rapidfuzz/distance/_initialize_cpp.pyx @@ -500,15 +500,27 @@ cdef class Editops: def __len__(self): return self.editops.size() - def __delitem__(self, item): - cdef Py_ssize_t index = item - if index < 0: - index += self.editops.size() + def __delitem__(self, key): + cdef Py_ssize_t index + cdef Py_ssize_t start, stop, step - if index < 0 or index >= self.editops.size(): - raise IndexError("Editops index out of range") + if isinstance(key, int): + index = key + if index < 0: + index += self.editops.size() - self.editops.erase(self.editops.begin() + index) + if index < 0 or index >= self.editops.size(): + raise IndexError("Editops index out of range") + + self.editops.erase(self.editops.begin() + index) + elif isinstance(key, slice): + start, stop, step = key.indices(self.editops.size()) + if step < 0: + raise ValueError("step sizes below 0 lead to an invalid order of editops") + + self.editops.remove_slice(start, stop, step) + else: + raise TypeError("Expected index or slice") def __getitem__(self, key): cdef Py_ssize_t index @@ -535,7 +547,7 @@ cdef class Editops: (x).editops = self.editops.slice(start, stop, step) return x else: - raise TypeError("Expected index") + raise TypeError("Expected index or slice") def __repr__(self): return "Editops([" + ", ".join(repr(op) for op in self) + f"], src_len={self.editops.get_src_len()}, dest_len={self.editops.get_dest_len()})" diff --git a/tests/distance/test_init.py b/tests/distance/test_init.py index 2446ff2..d5b9cce 100644 --- a/tests/distance/test_init.py +++ b/tests/distance/test_init.py @@ -71,6 +71,7 @@ def test_editops_get_index(): with pytest.raises(IndexError): ops[-6] + def test_editops_get_slice(): """ test __getitem__ with slice of Editops @@ -110,6 +111,53 @@ def test_editops_get_slice(): with pytest.raises(ValueError): ops[::-1] + +def test_editops_del_slice(): + """ + test __delitem__ with slice of Editops + """ + ops = Editops( + [ + ("delete", 1, 1), + ("replace", 2, 1), + ("insert", 6, 5), + ("insert", 6, 6), + ("insert", 6, 7), + ], + 7, + 9, + ) + + ops_list = [ + ("delete", 1, 1), + ("replace", 2, 1), + ("insert", 6, 5), + ("insert", 6, 6), + ("insert", 6, 7), + ] + + def del_test(key): + _ops = ops[::] + _ops_list = ops_list[::] + del _ops[key] + del _ops_list[key] + assert _ops.as_list() == _ops_list + + del_test(slice(None, 4, None)) + del_test(slice(1, None, None)) + del_test(slice(1, 4, None)) + del_test(slice(None, 4, 2)) + del_test(slice(1, None, 2)) + del_test(slice(1, 4, 2)) + + del_test(slice(None, -1, None)) + del_test(slice(-4, None, None)) + del_test(slice(-4, -1, None)) + del_test(slice(None, -1, 2)) + del_test(slice(-4, None, 2)) + del_test(slice(-4, -1, 2)) + + def test_editops_inversion(): """ test correct inversion of Editops