From dda99e6c9f753f3c45072d05b35d691e042cd8db Mon Sep 17 00:00:00 2001 From: Jesun Ahmad Ushno Date: Tue, 17 Feb 2026 05:13:12 -0500 Subject: [PATCH 1/2] Fix typing and lint issues --- .../hashing/hash_table_with_linked_list.py | 2 +- graphs/check_bipatrite.py | 29 +++++++++---------- .../linear_discriminant_analysis.py | 12 +++----- searches/jump_search.py | 7 ++--- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/data_structures/hashing/hash_table_with_linked_list.py b/data_structures/hashing/hash_table_with_linked_list.py index f404c5251246..c8dffa30b8e8 100644 --- a/data_structures/hashing/hash_table_with_linked_list.py +++ b/data_structures/hashing/hash_table_with_linked_list.py @@ -8,7 +8,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _set_value(self, key, data): - self.values[key] = deque([]) if self.values[key] is None else self.values[key] + self.values[key] = deque() if self.values[key] is None else self.values[key] self.values[key].appendleft(data) self._keys[key] = self.values[key] diff --git a/graphs/check_bipatrite.py b/graphs/check_bipatrite.py index 897c78850d58..ba988f788388 100644 --- a/graphs/check_bipatrite.py +++ b/graphs/check_bipatrite.py @@ -1,18 +1,20 @@ from collections import defaultdict, deque +from collections.abc import Hashable, Iterable, Mapping -def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool: +def is_bipartite_dfs(graph: Mapping[Hashable, Iterable[Hashable]]) -> bool: """ Check if a graph is bipartite using depth-first search (DFS). Args: - `graph`: Adjacency list representing the graph. + `graph`: Mapping of nodes to their neighbors. Nodes must be hashable. Returns: ``True`` if bipartite, ``False`` otherwise. Checks if the graph can be divided into two sets of vertices, such that no two - vertices within the same set are connected by an edge. + vertices within the same set are connected by an edge. Neighbor nodes that do + not appear as keys are treated as isolated nodes with no outgoing edges. Examples: @@ -33,7 +35,6 @@ def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool: >>> is_bipartite_dfs({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) False - >>> # FIXME: This test should fails with KeyError: 4. >>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) False >>> is_bipartite_dfs({0: [-1, 3], 1: [0, -2]}) @@ -43,8 +44,6 @@ def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool: >>> is_bipartite_dfs({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) True - >>> # FIXME: This test should fails with - >>> # TypeError: list indices must be integers or... >>> is_bipartite_dfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) True >>> is_bipartite_dfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) @@ -53,7 +52,7 @@ def is_bipartite_dfs(graph: dict[int, list[int]]) -> bool: True """ - def depth_first_search(node: int, color: int) -> bool: + def depth_first_search(node: Hashable, color: int) -> bool: """ Perform Depth-First Search (DFS) on the graph starting from a node. @@ -74,25 +73,26 @@ def depth_first_search(node: int, color: int) -> bool: return False return visited[node] == color - visited: defaultdict[int, int] = defaultdict(lambda: -1) + visited: defaultdict[Hashable, int] = defaultdict(lambda: -1) for node in graph: if visited[node] == -1 and not depth_first_search(node, 0): return False return True -def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool: +def is_bipartite_bfs(graph: Mapping[Hashable, Iterable[Hashable]]) -> bool: """ Check if a graph is bipartite using a breadth-first search (BFS). Args: - `graph`: Adjacency list representing the graph. + `graph`: Mapping of nodes to their neighbors. Nodes must be hashable. Returns: ``True`` if bipartite, ``False`` otherwise. Check if the graph can be divided into two sets of vertices, such that no two - vertices within the same set are connected by an edge. + vertices within the same set are connected by an edge. Neighbor nodes that do + not appear as keys are treated as isolated nodes with no outgoing edges. Examples: @@ -113,7 +113,6 @@ def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool: >>> is_bipartite_bfs({7: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 4: [0]}) False - >>> # FIXME: This test should fails with KeyError: 4. >>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]}) False >>> is_bipartite_bfs({0: [-1, 3], 1: [0, -2]}) @@ -123,8 +122,6 @@ def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool: >>> is_bipartite_bfs({0.9: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) True - >>> # FIXME: This test should fails with - >>> # TypeError: list indices must be integers or... >>> is_bipartite_bfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]}) True >>> is_bipartite_bfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]}) @@ -132,10 +129,10 @@ def is_bipartite_bfs(graph: dict[int, list[int]]) -> bool: >>> is_bipartite_bfs({0: ["b", "d"], 1: ["a", "c"], 2: ["b", "d"], 3: ["a", "c"]}) True """ - visited: defaultdict[int, int] = defaultdict(lambda: -1) + visited: defaultdict[Hashable, int] = defaultdict(lambda: -1) for node in graph: if visited[node] == -1: - queue: deque[int] = deque() + queue: deque[Hashable] = deque() queue.append(node) visited[node] = 0 while queue: diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 8528ccbbae51..b99bffc35d8b 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -47,7 +47,6 @@ from math import log from os import name, system from random import gauss, seed -from typing import TypeVar # Make a training dataset drawn from a gaussian distribution @@ -249,16 +248,13 @@ def accuracy(actual_y: list, predicted_y: list) -> float: return (correct / len(actual_y)) * 100 -num = TypeVar("num") - - -def valid_input( - input_type: Callable[[object], num], # Usually float or int +def valid_input[T]( + input_type: Callable[[object], T], # Usually float or int input_msg: str, err_msg: str, - condition: Callable[[num], bool] = lambda _: True, + condition: Callable[[T], bool] = lambda _: True, default: str | None = None, -) -> num: +) -> T: """ Ask for user value and validate that it fulfill a condition. diff --git a/searches/jump_search.py b/searches/jump_search.py index e72d85e8a868..437faf306bb2 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -10,17 +10,14 @@ import math from collections.abc import Sequence -from typing import Any, Protocol, TypeVar +from typing import Any, Protocol class Comparable(Protocol): def __lt__(self, other: Any, /) -> bool: ... -T = TypeVar("T", bound=Comparable) - - -def jump_search(arr: Sequence[T], item: T) -> int: +def jump_search[T: Comparable](arr: Sequence[T], item: T) -> int: """ Python implementation of the jump search algorithm. Return the index if the `item` is found, otherwise return -1. From c8d6babae764cb10e24c6b127c6914f2d7123fb3 Mon Sep 17 00:00:00 2001 From: Jesun Ahmad Ushno Date: Tue, 17 Feb 2026 05:45:02 -0500 Subject: [PATCH 2/2] Enhance sentinel_linear_search doctests with edge cases and type variations --- searches/sentinel_linear_search.py | 38 +++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/searches/sentinel_linear_search.py b/searches/sentinel_linear_search.py index 69c1cf9f351a..7066298d800b 100644 --- a/searches/sentinel_linear_search.py +++ b/searches/sentinel_linear_search.py @@ -14,10 +14,17 @@ def sentinel_linear_search(sequence, target): """Pure implementation of sentinel linear search algorithm in Python - :param sequence: some sequence with comparable items + :param sequence: a mutable list with comparable items. Note: this function + temporarily mutates the input list by appending and then removing the + target value, but the list is restored to its original state. :param target: item value to search :return: index of found item or None if item is not found + The sentinel linear search algorithm works by appending the target value to the + end of the list before searching. This eliminates the need for boundary checking + during the loop, as the search is guaranteed to find the target (at minimum, at + the end where the sentinel is placed). The input list is restored after the search. + Examples: >>> sentinel_linear_search([0, 5, 7, 10, 15], 0) 0 @@ -30,6 +37,35 @@ def sentinel_linear_search(sequence, target): >>> sentinel_linear_search([0, 5, 7, 10, 15], 6) + >>> sentinel_linear_search([1], 1) + 0 + + >>> sentinel_linear_search([1], 99) + + >>> sentinel_linear_search([], 5) + + >>> sentinel_linear_search([3, 1, 4, 1, 5, 9, 2, 6], 9) + 5 + + >>> sentinel_linear_search([3, 1, 4, 1, 5, 9, 2, 6], 1) + 1 + + >>> sentinel_linear_search(['a', 'b', 'c', 'd'], 'c') + 2 + + >>> sentinel_linear_search(['a', 'b', 'c', 'd'], 'z') + + >>> sentinel_linear_search([1.5, 2.7, 3.14, 4.0], 3.14) + 2 + + >>> sentinel_linear_search([1.5, 2.7, 3.14, 4.0], 2.5) + + >>> sentinel_linear_search([-10, -5, 0, 5, 10], -5) + 1 + + >>> sentinel_linear_search([-10, -5, 0, 5, 10], 0) + 2 + """ sequence.append(target)