diff --git a/src/book/chapter2/problem3.py b/src/book/chapter2/problem3.py index e0ca6d4..0a81d9a 100644 --- a/src/book/chapter2/problem3.py +++ b/src/book/chapter2/problem3.py @@ -17,7 +17,7 @@ def horner(A: Array[float], n: int, x: float) -> float: Returns: The value of the polynomial for x. """ - p = 0 + p = 0.0 for i in range_of(n, downto=0): p = A[i] + x * p return p diff --git a/src/book/chapter2/section3.py b/src/book/chapter2/section3.py index 13d4651..1583901 100644 --- a/src/book/chapter2/section3.py +++ b/src/book/chapter2/section3.py @@ -79,7 +79,7 @@ def insertion_sort_with_binary_search(A: Array[CT], n: int) -> None: A[k] = key -def __binary_search_position(A: Array[CT], x: int, p: int, r: int) -> int: +def __binary_search_position(A: Array[CT], x: CT, p: int, r: int) -> int: if p > r: return p q = (p + r) // 2 diff --git a/src/book/chapter5/problem2.py b/src/book/chapter5/problem2.py index 8a089fc..b11a0e0 100644 --- a/src/book/chapter5/problem2.py +++ b/src/book/chapter5/problem2.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from book.chapter5.section3 import randomly_permute from book.data_structures import Array @@ -6,7 +6,7 @@ from util import range_of -def deterministic_search(A: Array[T], n: int, x: T) -> Optional[int]: +def deterministic_search(A: Array[T], n: int, x: T) -> int | None: """Searches for a value in an array, examining its elements sequentially. Implements: @@ -26,7 +26,7 @@ def deterministic_search(A: Array[T], n: int, x: T) -> Optional[int]: return None -def scramble_search(A: Array[T], n: int, x: T) -> Optional[int]: +def scramble_search(A: Array[T], n: int, x: T) -> int | None: """Searches for a value in an array by first permuting it, then examining its elements sequentially. Implements: diff --git a/src/book/chapter5/section3.py b/src/book/chapter5/section3.py index 899cd35..44cbb98 100644 --- a/src/book/chapter5/section3.py +++ b/src/book/chapter5/section3.py @@ -19,7 +19,7 @@ def randomized_hire_assistant(rank: Array[int], n: int) -> None: hire_assistant(rank, n) -def randomly_permute(A: Array[int], n: int) -> None: +def randomly_permute(A: Array[T], n: int) -> None: """Permutes an array in place, producing a uniform random permutation. Implements: @@ -35,7 +35,7 @@ def randomly_permute(A: Array[int], n: int) -> None: A[i], A[j] = A[j], A[i] -def permute_without_identity(A: Array[int], n: int) -> None: +def permute_without_identity(A: Array[T], n: int) -> None: """A deliberately faulty algorithm for producing any permutation of an array without the identity permutation. Fails to produce any permutation in which an element on a given position occupied the same position in the original array. @@ -103,7 +103,7 @@ def random_sample(m: int, n: int) -> set[int]: m: the cardinality of the sample n: the cardinality of the set, 0 <= m <= n """ - S = set() + S = set[int]() for k in range_of(n - m + 1, to=n): i = random(1, k) if i in S: diff --git a/src/book/data_structures.py b/src/book/data_structures.py index 0ee22eb..aa32412 100644 --- a/src/book/data_structures.py +++ b/src/book/data_structures.py @@ -1,29 +1,37 @@ +from __future__ import annotations + from builtins import len from collections.abc import Callable from typing import Any from typing import Generic from typing import Literal from typing import TypeVar -from typing import Union from typing_extensions import Protocol -from typing_extensions import Self - T = TypeVar('T') +CT = TypeVar('CT', bound='Comparable') class Comparable(Protocol): + def __lt__(self, other: Any) -> bool: + pass + def __le__(self, other: Any) -> bool: pass + def __gt__(self, other: Any) -> bool: + pass + + def __ge__(self, other: Any) -> bool: + pass + class Indexable(Protocol[T]): __getitem__: Callable[[int], T] __setitem__: Callable[[int, T], None] -CT = TypeVar('CT', bound=Comparable) Bit = Literal[0, 1] @@ -34,7 +42,7 @@ class Array(Generic[T]): def __init__(self, start: int, end: int) -> None: assert 0 <= start <= end self.__start = start - self.__elements = [None] * (end - start + 1) + self.__elements = [None] * (end - start + 1) # type: ignore def __getitem__(self, index: int) -> T: assert self.__start <= index < self.__start + len(self.__elements) @@ -61,10 +69,10 @@ class Matrix: __end_row: int __start_col: int __end_col: int - __elements: list[list[Union[int, float]]] + __elements: list[list[int | float]] def __init__(self, end_row: int, end_col: int, start_row: int = 1, start_col: int = 1, - elements: list[list[Union[int, float]]] = None) -> None: + elements: list[list[int | float]] | None = None) -> None: if elements is None: assert start_row == 1 assert start_col == 1 @@ -74,7 +82,7 @@ def __init__(self, end_row: int, end_col: int, start_row: int = 1, start_col: in self.__start_row, self.__end_row = start_row, end_row self.__start_col, self.__end_col = start_col, end_col - def submatrix(self, rows: tuple[int, int], cols: tuple[int, int]) -> Self: + def submatrix(self, rows: tuple[int, int], cols: tuple[int, int]) -> "Matrix": assert 1 <= rows[0] <= rows[1] <= len(self.__elements) assert 1 <= cols[0] <= cols[1] <= len(self.__elements[0]) rows_shifted = rows[0] + self.__start_row - 1, rows[1] + self.__start_row - 1 @@ -82,20 +90,20 @@ def submatrix(self, rows: tuple[int, int], cols: tuple[int, int]) -> Self: return Matrix(start_row=rows_shifted[0], end_row=rows_shifted[1], start_col=cols_shifted[0], end_col=cols_shifted[1], elements=self.__elements) - def even_rows_submatrix(self) -> Self: + def even_rows_submatrix(self) -> "Matrix": even_rows = [row for i, row in enumerate(self.__elements, start=1) if i % 2 == 0] submatrix_end_row = self.__start_row + len(even_rows) - 1 return Matrix(start_row=self.__start_row, end_row=submatrix_end_row, start_col=self.__start_col, end_col=self.__end_col, elements=even_rows) - def __getitem__(self, indices: tuple[int, int]) -> Union[int, float]: + def __getitem__(self, indices: tuple[int, int]) -> int | float: row = indices[0] col = indices[1] assert 1 <= row <= self.__end_row - self.__start_row + 1 assert 1 <= col <= self.__end_col - self.__start_col + 1 return self.__elements[self.__start_row - 1 + row - 1][self.__start_col - 1 + col - 1] - def __setitem__(self, indices: tuple[int, int], value: Union[int, float]) -> None: + def __setitem__(self, indices: tuple[int, int], value: int | float) -> None: row = indices[0] col = indices[1] assert 1 <= row <= self.__end_row - self.__start_row + 1 diff --git a/src/solutions/chapter2/problem3.py b/src/solutions/chapter2/problem3.py index 208ec30..e3445b1 100644 --- a/src/solutions/chapter2/problem3.py +++ b/src/solutions/chapter2/problem3.py @@ -17,7 +17,7 @@ def polynomial_evaluate(A: Array[float], n: int, x: float) -> float: Returns: The value of the polynomial for x. """ - p = 0 + p = 0.0 for i in range_of(0, to=n): s = A[i] for j in range_of(1, to=i): diff --git a/src/solutions/chapter2/section1/exercise4.py b/src/solutions/chapter2/section1/exercise4.py index 68b967b..907e1f9 100644 --- a/src/solutions/chapter2/section1/exercise4.py +++ b/src/solutions/chapter2/section1/exercise4.py @@ -1,11 +1,10 @@ -from typing import Optional +from __future__ import annotations from book.data_structures import Array -from book.data_structures import T from util import range_of -def linear_search(A: Array[T], n: int, x: int) -> Optional[int]: +def linear_search(A: Array[int], n: int, x: int) -> int | None: """Searches for a number in a sequence of numbers using linear search. Implements: diff --git a/src/solutions/chapter2/section1/exercise5.py b/src/solutions/chapter2/section1/exercise5.py index 3bb6caa..5b32faa 100644 --- a/src/solutions/chapter2/section1/exercise5.py +++ b/src/solutions/chapter2/section1/exercise5.py @@ -20,7 +20,7 @@ def add_binary_integers(A: Array[Bit], B: Array[Bit], n: int) -> Array[Bit]: C = Array[Bit](0, n) carry = 0 for i in range_of(0, to=n - 1): - C[i] = (A[i] + B[i] + carry) % 2 + C[i] = (A[i] + B[i] + carry) % 2 # type: ignore carry = (A[i] + B[i] + carry) // 2 - C[n] = carry + C[n] = carry # type: ignore return C diff --git a/src/solutions/chapter2/section3/exercise6.py b/src/solutions/chapter2/section3/exercise6.py index 387811f..0802cde 100644 --- a/src/solutions/chapter2/section3/exercise6.py +++ b/src/solutions/chapter2/section3/exercise6.py @@ -1,10 +1,9 @@ -from typing import Optional +from __future__ import annotations from book.data_structures import Array -from book.data_structures import CT -def binary_search(A: Array[CT], p: int, r: int, x: int) -> Optional[int]: +def binary_search(A: Array[int], p: int, r: int, x: int) -> int | None: """Searches for a number in a sorted sequence of numbers using binary search. Implements: diff --git a/src/solutions/chapter4/problem7.py b/src/solutions/chapter4/problem7.py index 5ab72b8..468851a 100644 --- a/src/solutions/chapter4/problem7.py +++ b/src/solutions/chapter4/problem7.py @@ -1,5 +1,4 @@ -from typing import Optional -from typing import Union +from __future__ import annotations from book.data_structures import Array from book.data_structures import Matrix @@ -7,7 +6,7 @@ from util import range_of -def monge_leftmost_minimums(A: Matrix, m: int, n: int) -> Array[Union[int, float]]: +def monge_leftmost_minimums(A: Matrix, m: int, n: int) -> Array[int | float]: """Finds the leftmost minimum in each row of a Monge array. Args: @@ -25,14 +24,14 @@ def monge_leftmost_minimums(A: Matrix, m: int, n: int) -> Array[Union[int, float return minimums -def __monge_leftmost_minimums_indices(A: Matrix, m: int, n: int) -> Optional[Array[Union[int, float]]]: +def __monge_leftmost_minimums_indices(A: Matrix, m: int, n: int) -> Array[int] | None: if m == 0: return None A_ = A.even_rows_submatrix() even_rows_leftmost_minimums_indices = __monge_leftmost_minimums_indices(A_, m // 2, n) odd_rows_leftmost_minimums_indices = __monge_odd_rows_leftmost_minimums_indices(A, m, n, even_rows_leftmost_minimums_indices) - indices = Array(1, m) + indices = Array[int](1, m) for i in range_of(1, to=m // 2): indices[2 * i] = even_rows_leftmost_minimums_indices[i] for i in range_of(1, to=ceil_div(m, 2)): @@ -40,9 +39,11 @@ def __monge_leftmost_minimums_indices(A: Matrix, m: int, n: int) -> Optional[Arr return indices -def __monge_odd_rows_leftmost_minimums_indices(A: Matrix, m: int, n: int, - even_rows_leftmost_minimums_indices: Array[Union[int, float]]) \ - -> Array[Union[int, float]]: +def __monge_odd_rows_leftmost_minimums_indices(A: Matrix, + m: int, + n: int, + even_rows_leftmost_minimums_indices: Array[int])\ + -> Array[int | float]: odd_rows_leftmost_minimums_indices = Array(1, ceil_div(m, 2)) for i in range_of(1, to=ceil_div(m, 2)): prev_minimum_index = even_rows_leftmost_minimums_indices[i - 1] if i > 1 else 1 diff --git a/src/solutions/chapter5/problem2.py b/src/solutions/chapter5/problem2.py index 9a24caf..e19d180 100644 --- a/src/solutions/chapter5/problem2.py +++ b/src/solutions/chapter5/problem2.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from book.data_structures import Array from book.data_structures import T @@ -6,7 +6,7 @@ from util import range_of -def random_search(A: Array[T], n: int, x: T) -> Optional[int]: +def random_search(A: Array[T], n: int, x: T) -> int | None: """Searches for an element in a sequence of elements by randomly choosing next element to check. Implements: @@ -20,7 +20,7 @@ def random_search(A: Array[T], n: int, x: T) -> Optional[int]: Returns: An index i such that x equals A[i] or None if x does not appear in A. """ - picked = Array(1, n) + picked = Array[bool](1, n) for i in range_of(1, to=n): picked[i] = False k = 0 diff --git a/src/util.py b/src/util.py index 6cc7ddf..d7790be 100644 --- a/src/util.py +++ b/src/util.py @@ -1,14 +1,18 @@ +from __future__ import annotations + from collections.abc import Iterable -from typing import Union -def range_of(_from: int, to: int = None, downto: int = None, by: int = None) -> Iterable: - if downto is None: +def range_of(_from: int, + to: int | None = None, + downto: int | None = None, + by: int | None = None) -> Iterable: + if to is not None and downto is None: return range(_from, to + 1, 1 if by is None else by) - if to is None and by is None: + if to is None and downto is not None and by is None: return range(_from, downto - 1, -1) - AssertionError('Invalid range bounds') + raise AssertionError('Invalid range bounds') -def ceil_div(x: Union[int, float], y: Union[int, float]) -> int: +def ceil_div(x: int, y: int) -> int: return -(x // -y) diff --git a/test/test_case.py b/test/test_case.py index 9699b42..973c1b1 100644 --- a/test/test_case.py +++ b/test/test_case.py @@ -13,7 +13,7 @@ class ClrsTestCase(unittest.TestCase): - def assertArraySorted(self, array: Array[CT], start: int = 1, end: int = None, + def assertArraySorted(self, array: Array[CT], end: int, start: int = 1, cmp: Callable[[Any, Any], bool] = operator.le) -> None: if start >= end: return @@ -22,7 +22,7 @@ def assertArraySorted(self, array: Array[CT], start: int = 1, end: int = None, self.fail(f'Array {array} is not sorted according to the relation {cmp}: ' f'elements ${array[i]}, ${array[i + 1]} are out of order') - def assertArrayPermuted(self, array: Array[T], elements: list[T], start: int = 1, end: int = None) -> None: + def assertArrayPermuted(self, array: Array[T], elements: list[T], end: int, start: int = 1) -> None: msg = f'Array {array} is not a permutation of {elements}' if end - start + 1 != len(elements): self.fail(msg) diff --git a/test/test_util.py b/test/test_util.py index 1d68e14..7958da0 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,4 +1,4 @@ -from typing import Union +from __future__ import annotations from book.data_structures import Array from book.data_structures import Bit @@ -17,7 +17,7 @@ def create_array(elements: list[T], start: int = 1) -> Array[T]: return array -def create_matrix(elements: list[list[Union[int, float]]]) -> Matrix: +def create_matrix(elements: list[list[int | float]]) -> Matrix: rows = len(elements) cols = len(elements[0]) matrix = Matrix(rows, cols)