From 68f876afef05a2eadd546135004b218b4c14eec2 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:03:10 -0800 Subject: [PATCH 01/38] Add narcissistic number finder with dynamic programming (#13971) * Add narcissistic number finder with dynamic programming * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update dynamic_programming/narcissistic_number.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dynamic_programming/narcissistic_number.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dynamic_programming/narcissistic_number.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dynamic_programming/narcissistic_number.py | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 dynamic_programming/narcissistic_number.py diff --git a/dynamic_programming/narcissistic_number.py b/dynamic_programming/narcissistic_number.py new file mode 100644 index 000000000000..dc1c6f5a5660 --- /dev/null +++ b/dynamic_programming/narcissistic_number.py @@ -0,0 +1,103 @@ +""" +Find all narcissistic numbers up to a given limit using dynamic programming. + +A narcissistic number (also known as an Armstrong number or plus perfect number) +is a number that is the sum of its own digits each raised to the power of the +number of digits. + +For example, 153 is a narcissistic number because 153 = 1^3 + 5^3 + 3^3. + +This implementation uses dynamic programming with memoization to efficiently +compute digit powers and find all narcissistic numbers up to a specified limit. + +The DP optimization caches digit^power calculations. When searching through many +numbers, the same digit power calculations occur repeatedly (e.g., 153, 351, 135 +all need 1^3, 5^3, 3^3). Memoization avoids these redundant calculations. + +Examples of narcissistic numbers: + Single digit: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + Three digit: 153, 370, 371, 407 + Four digit: 1634, 8208, 9474 + Five digit: 54748, 92727, 93084 + +Reference: https://en.wikipedia.org/wiki/Narcissistic_number +""" + + +def find_narcissistic_numbers(limit: int) -> list[int]: + """ + Find all narcissistic numbers up to the given limit using dynamic programming. + + This function uses memoization to cache digit power calculations, avoiding + redundant computations across different numbers with the same digit count. + + Args: + limit: The upper bound for searching narcissistic numbers (exclusive) + + Returns: + list[int]: A sorted list of all narcissistic numbers below the limit + + Examples: + >>> find_narcissistic_numbers(10) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> find_narcissistic_numbers(160) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153] + >>> find_narcissistic_numbers(400) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371] + >>> find_narcissistic_numbers(1000) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407] + >>> find_narcissistic_numbers(10000) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474] + >>> find_narcissistic_numbers(1) + [0] + >>> find_narcissistic_numbers(0) + [] + """ + if limit <= 0: + return [] + + narcissistic_nums = [] + + # Memoization: cache[(power, digit)] = digit^power + # This avoids recalculating the same power for different numbers + power_cache: dict[tuple[int, int], int] = {} + + def get_digit_power(digit: int, power: int) -> int: + """Get digit^power using memoization (DP optimization).""" + if (power, digit) not in power_cache: + power_cache[(power, digit)] = digit**power + return power_cache[(power, digit)] + + # Check each number up to the limit + for number in range(limit): + # Count digits + num_digits = len(str(number)) + + # Calculate sum of powered digits using memoized powers + remaining = number + digit_sum = 0 + while remaining > 0: + digit = remaining % 10 + digit_sum += get_digit_power(digit, num_digits) + remaining //= 10 + + # Check if narcissistic + if digit_sum == number: + narcissistic_nums.append(number) + + return narcissistic_nums + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Demonstrate the dynamic programming approach + print("Finding all narcissistic numbers up to 10000:") + print("(Using memoization to cache digit power calculations)") + print() + + narcissistic_numbers = find_narcissistic_numbers(10000) + print(f"Found {len(narcissistic_numbers)} narcissistic numbers:") + print(narcissistic_numbers) From 3c887358e59bcfa0c1cc6df840e6781d9a8efac9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:57:27 +0100 Subject: [PATCH 02/38] [pre-commit.ci] pre-commit autoupdate (#14078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.7 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.7...v0.14.10) - [github.com/pre-commit/mirrors-mypy: v1.19.0 → v1.19.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.0...v1.19.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 4 ++-- DIRECTORY.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57f92ce941d9..75e43225f588 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.7 + rev: v0.14.10 hooks: - id: ruff-check - id: ruff-format @@ -50,7 +50,7 @@ repos: - id: validate-pyproject - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.0 + rev: v1.19.1 hooks: - id: mypy args: diff --git a/DIRECTORY.md b/DIRECTORY.md index 0f9859577493..29dde5b106cb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -398,6 +398,7 @@ * [Minimum Squares To Represent A Number](dynamic_programming/minimum_squares_to_represent_a_number.py) * [Minimum Steps To One](dynamic_programming/minimum_steps_to_one.py) * [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py) + * [Narcissistic Number](dynamic_programming/narcissistic_number.py) * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py) * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py) * [Range Sum Query](dynamic_programming/range_sum_query.py) From ca5b8c156be42b7033c8849b570a0a59b77b8eab Mon Sep 17 00:00:00 2001 From: Parag Sharma Date: Wed, 21 Jan 2026 21:31:41 +0530 Subject: [PATCH 03/38] Add sliding window maximum using monotonic deque (#14133) * feat: add sliding window maximum using monotonic deque * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat: add sliding window maximum using monotonic deque --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- other/sliding_window_maximum.py | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 other/sliding_window_maximum.py diff --git a/other/sliding_window_maximum.py b/other/sliding_window_maximum.py new file mode 100644 index 000000000000..1c2c3c8e37e6 --- /dev/null +++ b/other/sliding_window_maximum.py @@ -0,0 +1,58 @@ +from collections import deque + + +def sliding_window_maximum(numbers: list[int], window_size: int) -> list[int]: + """ + Return a list containing the maximum of each sliding window of size window_size. + + This implementation uses a monotonic deque to achieve O(n) time complexity. + + Args: + numbers: List of integers representing the input array. + window_size: Size of the sliding window (must be positive). + + Returns: + List of maximum values for each valid window. + + Raises: + ValueError: If window_size is not a positive integer. + + Time Complexity: O(n) - each element is added and removed at most once + Space Complexity: O(k) - deque stores at most window_size indices + + Examples: + >>> sliding_window_maximum([1, 3, -1, -3, 5, 3, 6, 7], 3) + [3, 3, 5, 5, 6, 7] + >>> sliding_window_maximum([9, 11], 2) + [11] + >>> sliding_window_maximum([], 3) + [] + >>> sliding_window_maximum([4, 2, 12, 3], 1) + [4, 2, 12, 3] + >>> sliding_window_maximum([1], 1) + [1] + """ + if window_size <= 0: + raise ValueError("Window size must be a positive integer") + if not numbers: + return [] + + result: list[int] = [] + index_deque: deque[int] = deque() + + for current_index, current_value in enumerate(numbers): + # Remove the element which is out of this window + if index_deque and index_deque[0] == current_index - window_size: + index_deque.popleft() + + # Remove useless elements (smaller than current) from back + while index_deque and numbers[index_deque[-1]] < current_value: + index_deque.pop() + + index_deque.append(current_index) + + # Start adding to result once we have a full window + if current_index >= window_size - 1: + result.append(numbers[index_deque[0]]) + + return result From 8fa4161587ab931adb65392a2bcd6bebe5e061f3 Mon Sep 17 00:00:00 2001 From: Tithi Joshi Date: Sun, 25 Jan 2026 19:24:15 +0530 Subject: [PATCH 04/38] docs: improve docstring clarity in reverse_words (#14212) Updated function name and docstring for clarity. --- strings/reverse_words.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/strings/reverse_words.py b/strings/reverse_words.py index 504c1c2089dd..bee237f2a2d9 100644 --- a/strings/reverse_words.py +++ b/strings/reverse_words.py @@ -1,12 +1,14 @@ -def reverse_words(input_str: str) -> str: - """ - Reverses words in a given string +def reverse_words(sentence: str) -> str: + """Reverse the order of words in a given string. + + Extra whitespace between words is ignored. + >>> reverse_words("I love Python") 'Python love I' >>> reverse_words("I Love Python") 'Python Love I' """ - return " ".join(input_str.split()[::-1]) + return " ".join(sentence.split()[::-1]) if __name__ == "__main__": From 8106aea67f0248479908eed313911c23c631b6c7 Mon Sep 17 00:00:00 2001 From: Parth Pawar Date: Sun, 25 Jan 2026 19:33:25 +0530 Subject: [PATCH 05/38] Correct typo from 'two large' to 'too large' (#14135) Fix typo in documentation regarding shift size. --- ciphers/caesar_cipher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 1cf4d67cbaed..ef5f49313ee7 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -45,7 +45,7 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str: And our shift is ``2`` We can then encode the message, one letter at a time. ``H`` would become ``J``, - since ``J`` is two letters away, and so on. If the shift is ever two large, or + since ``J`` is two letters away, and so on. If the shift is ever too large, or our letter is at the end of the alphabet, we just start at the beginning (``Z`` would shift to ``a`` then ``b`` and so on). From 678dedbbf94be54b3c9c258368e28bb8e7736d62 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:20:07 +0100 Subject: [PATCH 06/38] [pre-commit.ci] pre-commit autoupdate (#14238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/MarcoGorelli/auto-walrus: 0.3.4 → 0.4.1](https://github.com/MarcoGorelli/auto-walrus/compare/0.3.4...0.4.1) - [github.com/astral-sh/ruff-pre-commit: v0.14.10 → v0.14.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.10...v0.14.14) - [github.com/tox-dev/pyproject-fmt: v2.11.1 → v2.12.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.11.1...v2.12.1) * updating DIRECTORY.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] --- .pre-commit-config.yaml | 6 +++--- DIRECTORY.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75e43225f588..765d5cff38d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,12 +14,12 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/MarcoGorelli/auto-walrus - rev: 0.3.4 + rev: 0.4.1 hooks: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.10 + rev: v0.14.14 hooks: - id: ruff-check - id: ruff-format @@ -32,7 +32,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.11.1 + rev: v2.12.1 hooks: - id: pyproject-fmt diff --git a/DIRECTORY.md b/DIRECTORY.md index 29dde5b106cb..a73c630bc8a7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -881,6 +881,7 @@ * [Quine](other/quine.py) * [Scoring Algorithm](other/scoring_algorithm.py) * [Sdes](other/sdes.py) + * [Sliding Window Maximum](other/sliding_window_maximum.py) * [Tower Of Hanoi](other/tower_of_hanoi.py) * [Word Search](other/word_search.py) From ac05f1248c84f022f3f32df111554f04c36de77f Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:47:34 -0800 Subject: [PATCH 07/38] feat: add Jarvis March (Gift Wrapping) convex hull algorithm (#14225) * Add Jarvis March (Gift Wrapping) convex hull algorithm * Use descriptive parameter names per algorithms-keeper review * Update jarvis_march.py * Update jarvis_march.py * fix: add pytest marker * Update jarvis_march.py * feat: removed doctests and created a separate test file for CI to pass * Update jarvis_march_unit.py * Update jarvis_march.py * Update jarvis_march_unit.py * feat: added test folder with tests to pass CI checks * fix: duplicate points handled * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: fixed ruff errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../hashing/hash_table_with_linked_list.py | 2 +- geometry/jarvis_march.py | 187 ++++++++++++++++++ geometry/tests/__init__.py | 0 geometry/tests/test_jarvis_march.py | 115 +++++++++++ 4 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 geometry/jarvis_march.py create mode 100644 geometry/tests/__init__.py create mode 100644 geometry/tests/test_jarvis_march.py 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/geometry/jarvis_march.py b/geometry/jarvis_march.py new file mode 100644 index 000000000000..55a0872ff60e --- /dev/null +++ b/geometry/jarvis_march.py @@ -0,0 +1,187 @@ +""" +Jarvis March (Gift Wrapping) algorithm for finding the convex hull of a set of points. + +The convex hull is the smallest convex polygon that contains all the points. + +Time Complexity: O(n*h) where n is the number of points and h is the number of +hull points. +Space Complexity: O(h) where h is the number of hull points. + +USAGE: + -> Import this file into your project. + -> Use the jarvis_march() function to find the convex hull of a set of points. + -> Parameters: + -> points: A list of Point objects representing 2D coordinates + +REFERENCES: + -> Wikipedia reference: https://en.wikipedia.org/wiki/Gift_wrapping_algorithm + -> GeeksforGeeks: + https://www.geeksforgeeks.org/convex-hull-set-1-jarviss-algorithm-or-wrapping/ +""" + +from __future__ import annotations + + +class Point: + """Represents a 2D point with x and y coordinates.""" + + def __init__(self, x_coordinate: float, y_coordinate: float) -> None: + self.x = x_coordinate + self.y = y_coordinate + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Point): + return NotImplemented + return self.x == other.x and self.y == other.y + + def __repr__(self) -> str: + return f"Point({self.x}, {self.y})" + + def __hash__(self) -> int: + return hash((self.x, self.y)) + + +def _cross_product(origin: Point, point_a: Point, point_b: Point) -> float: + """ + Calculate the cross product of vectors OA and OB. + + Returns: + > 0: Counter-clockwise turn (left turn) + = 0: Collinear + < 0: Clockwise turn (right turn) + """ + return (point_a.x - origin.x) * (point_b.y - origin.y) - (point_a.y - origin.y) * ( + point_b.x - origin.x + ) + + +def _is_point_on_segment(p1: Point, p2: Point, point: Point) -> bool: + """Check if a point lies on the line segment between p1 and p2.""" + # Check if point is collinear with segment endpoints + cross = (point.y - p1.y) * (p2.x - p1.x) - (point.x - p1.x) * (p2.y - p1.y) + + if abs(cross) > 1e-9: + return False + + # Check if point is within the bounding box of the segment + return min(p1.x, p2.x) <= point.x <= max(p1.x, p2.x) and min( + p1.y, p2.y + ) <= point.y <= max(p1.y, p2.y) + + +def _find_leftmost_point(points: list[Point]) -> int: + """Find index of leftmost point (and bottom-most in case of tie).""" + left_idx = 0 + for i in range(1, len(points)): + if points[i].x < points[left_idx].x or ( + points[i].x == points[left_idx].x and points[i].y < points[left_idx].y + ): + left_idx = i + return left_idx + + +def _find_next_hull_point(points: list[Point], current_idx: int) -> int: + """Find the next point on the convex hull.""" + next_idx = (current_idx + 1) % len(points) + # Ensure next_idx is not the same as current_idx + while next_idx == current_idx: + next_idx = (next_idx + 1) % len(points) + + for i in range(len(points)): + if i == current_idx: + continue + cross = _cross_product(points[current_idx], points[i], points[next_idx]) + if cross > 0: + next_idx = i + + return next_idx + + +def _is_valid_polygon(hull: list[Point]) -> bool: + """Check if hull forms a valid polygon (has at least one non-collinear turn).""" + for i in range(len(hull)): + p1 = hull[i] + p2 = hull[(i + 1) % len(hull)] + p3 = hull[(i + 2) % len(hull)] + if abs(_cross_product(p1, p2, p3)) > 1e-9: + return True + return False + + +def _add_point_to_hull(hull: list[Point], point: Point) -> None: + """Add a point to hull, removing collinear intermediate points.""" + last = len(hull) - 1 + if len(hull) > 1 and _is_point_on_segment(hull[last - 1], hull[last], point): + hull[last] = Point(point.x, point.y) + else: + hull.append(Point(point.x, point.y)) + + +def jarvis_march(points: list[Point]) -> list[Point]: + """ + Find the convex hull of a set of points using the Jarvis March algorithm. + + The algorithm starts with the leftmost point and wraps around the set of + points, selecting the most counter-clockwise point at each step. + + Args: + points: List of Point objects representing 2D coordinates + + Returns: + List of Points that form the convex hull in counter-clockwise order. + Returns empty list if there are fewer than 3 non-collinear points. + """ + if len(points) <= 2: + return [] + + # Remove duplicate points to avoid infinite loops + unique_points = list(set(points)) + + if len(unique_points) <= 2: + return [] + + convex_hull: list[Point] = [] + + # Find the leftmost point + left_point_idx = _find_leftmost_point(unique_points) + convex_hull.append( + Point(unique_points[left_point_idx].x, unique_points[left_point_idx].y) + ) + + current_idx = left_point_idx + while True: + # Find the next counter-clockwise point + next_idx = _find_next_hull_point(unique_points, current_idx) + + if next_idx == left_point_idx: + break + + if next_idx == current_idx: + break + + current_idx = next_idx + _add_point_to_hull(convex_hull, unique_points[current_idx]) + + # Check for degenerate cases + if len(convex_hull) <= 2: + return [] + + # Check if last point is collinear with first and second-to-last + last = len(convex_hull) - 1 + if _is_point_on_segment(convex_hull[last - 1], convex_hull[last], convex_hull[0]): + convex_hull.pop() + if len(convex_hull) == 2: + return [] + + # Verify the hull forms a valid polygon + if not _is_valid_polygon(convex_hull): + return [] + + return convex_hull + + +if __name__ == "__main__": + # Example usage + points = [Point(0, 0), Point(1, 1), Point(0, 1), Point(1, 0), Point(0.5, 0.5)] + hull = jarvis_march(points) + print(f"Convex hull: {hull}") diff --git a/geometry/tests/__init__.py b/geometry/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/geometry/tests/test_jarvis_march.py b/geometry/tests/test_jarvis_march.py new file mode 100644 index 000000000000..6e7defe414a3 --- /dev/null +++ b/geometry/tests/test_jarvis_march.py @@ -0,0 +1,115 @@ +""" +Unit tests for Jarvis March (Gift Wrapping) algorithm. +""" + +from geometry.jarvis_march import Point, jarvis_march + + +class TestPoint: + """Tests for the Point class.""" + + def test_point_creation(self) -> None: + """Test Point initialization.""" + p = Point(1.0, 2.0) + assert p.x == 1.0 + assert p.y == 2.0 + + def test_point_equality(self) -> None: + """Test Point equality comparison.""" + p1 = Point(1.0, 2.0) + p2 = Point(1.0, 2.0) + p3 = Point(2.0, 1.0) + assert p1 == p2 + assert p1 != p3 + + def test_point_repr(self) -> None: + """Test Point string representation.""" + p = Point(1.5, 2.5) + assert repr(p) == "Point(1.5, 2.5)" + + def test_point_hash(self) -> None: + """Test Point hashing.""" + p1 = Point(1.0, 2.0) + p2 = Point(1.0, 2.0) + assert hash(p1) == hash(p2) + + +class TestJarvisMarch: + """Tests for the jarvis_march function.""" + + def test_triangle(self) -> None: + """Test convex hull of a triangle.""" + p1, p2, p3 = Point(1, 1), Point(2, 1), Point(1.5, 2) + hull = jarvis_march([p1, p2, p3]) + assert len(hull) == 3 + assert all(p in hull for p in [p1, p2, p3]) + + def test_collinear_points(self) -> None: + """Test that collinear points return empty hull.""" + points = [Point(i, 0) for i in range(5)] + hull = jarvis_march(points) + assert hull == [] + + def test_rectangle_with_interior_point(self) -> None: + """Test rectangle with interior point - interior point excluded.""" + p1, p2 = Point(1, 1), Point(2, 1) + p3, p4 = Point(2, 2), Point(1, 2) + p5 = Point(1.5, 1.5) + hull = jarvis_march([p1, p2, p3, p4, p5]) + assert len(hull) == 4 + assert p5 not in hull + + def test_star_shape(self) -> None: + """Test star shape - only tips are in hull.""" + tips = [ + Point(-5, 6), + Point(-11, 0), + Point(-9, -8), + Point(4, 4), + Point(6, -7), + ] + interior = [Point(-7, -2), Point(-2, -4), Point(0, 1)] + hull = jarvis_march(tips + interior) + assert len(hull) == 5 + assert all(p in hull for p in tips) + assert not any(p in hull for p in interior) + + def test_empty_list(self) -> None: + """Test empty list returns empty hull.""" + assert jarvis_march([]) == [] + + def test_single_point(self) -> None: + """Test single point returns empty hull.""" + assert jarvis_march([Point(0, 0)]) == [] + + def test_two_points(self) -> None: + """Test two points return empty hull.""" + assert jarvis_march([Point(0, 0), Point(1, 1)]) == [] + + def test_square(self) -> None: + """Test convex hull of a square.""" + p1, p2 = Point(0, 0), Point(1, 0) + p3, p4 = Point(1, 1), Point(0, 1) + hull = jarvis_march([p1, p2, p3, p4]) + assert len(hull) == 4 + assert all(p in hull for p in [p1, p2, p3, p4]) + + def test_duplicate_points(self) -> None: + """Test handling of duplicate points.""" + p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + points = [p1, p2, p3, p1, p2] # Include duplicates + hull = jarvis_march(points) + assert len(hull) == 3 + + def test_pentagon(self) -> None: + """Test convex hull of a pentagon.""" + points = [ + Point(0, 1), + Point(1, 2), + Point(2, 1), + Point(1.5, 0), + Point(0.5, 0), + ] + hull = jarvis_march(points) + assert len(hull) == 5 + assert all(p in hull for p in points) From e333ddb852f58c9083c7ef1180a4be9473f436dd Mon Sep 17 00:00:00 2001 From: John Law Date: Sat, 7 Mar 2026 20:29:05 +0000 Subject: [PATCH 08/38] chore: Fix ruff build failures (#14347) --- machine_learning/linear_discriminant_analysis.py | 6 +----- searches/jump_search.py | 7 ++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 8528ccbbae51..de2d1de46ba1 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,10 +248,7 @@ def accuracy(actual_y: list, predicted_y: list) -> float: return (correct / len(actual_y)) * 100 -num = TypeVar("num") - - -def valid_input( +def valid_input[num]( input_type: Callable[[object], num], # Usually float or int input_msg: str, err_msg: str, 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 f527d43d6d07a68c1b4691a21b5a316810bb3cb8 Mon Sep 17 00:00:00 2001 From: Ali Alimohammadi <41567902+AliAlimohammadi@users.noreply.github.com> Date: Sat, 7 Mar 2026 14:35:52 -0800 Subject: [PATCH 09/38] feat: add Graham Scan convex hull algorithm (#14251) * feat: add Graham Scan convex hull algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: address pre-commit issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore: Algorithm-Keeper's comments addressed --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: John Law Co-authored-by: Christian Clauss --- geometry/graham_scan.py | 246 ++++++++++++++++++++++++++ geometry/tests/test_graham_scan.py | 266 +++++++++++++++++++++++++++++ 2 files changed, 512 insertions(+) create mode 100644 geometry/graham_scan.py create mode 100644 geometry/tests/test_graham_scan.py diff --git a/geometry/graham_scan.py b/geometry/graham_scan.py new file mode 100644 index 000000000000..a48391dfbc5d --- /dev/null +++ b/geometry/graham_scan.py @@ -0,0 +1,246 @@ +""" +Graham Scan algorithm for finding the convex hull of a set of points. + +The Graham scan is a method of computing the convex hull of a finite set of points +in the plane with time complexity O(n log n). It is named after Ronald Graham, who +published the original algorithm in 1972. + +The algorithm finds all vertices of the convex hull ordered along its boundary. +It uses a stack to efficiently identify and remove points that would create +non-convex angles. + +References: +- https://en.wikipedia.org/wiki/Graham_scan +- Graham, R.L. (1972). "An Efficient Algorithm for Determining the Convex Hull of a + Finite Planar Set" +""" + +from __future__ import annotations + +from collections.abc import Sequence +from dataclasses import dataclass +from typing import TypeVar + +T = TypeVar("T", bound="Point") + + +@dataclass +class Point: + """ + A point in 2D space. + + >>> Point(0, 0) + Point(x=0.0, y=0.0) + >>> Point(1.5, 2.5) + Point(x=1.5, y=2.5) + """ + + x: float + y: float + + def __init__(self, x_coordinate: float, y_coordinate: float) -> None: + """ + Initialize a 2D point. + + Args: + x_coordinate: The x-coordinate (horizontal position) of the point + y_coordinate: The y-coordinate (vertical position) of the point + """ + self.x = float(x_coordinate) + self.y = float(y_coordinate) + + def __eq__(self, other: object) -> bool: + """ + Check if two points are equal. + + >>> Point(1, 2) == Point(1, 2) + True + >>> Point(1, 2) == Point(2, 1) + False + """ + if not isinstance(other, Point): + return NotImplemented + return self.x == other.x and self.y == other.y + + def __lt__(self, other: Point) -> bool: + """ + Compare two points for sorting (bottom-most, then left-most). + + >>> Point(1, 2) < Point(1, 3) + True + >>> Point(1, 2) < Point(2, 2) + True + >>> Point(2, 2) < Point(1, 2) + False + """ + if self.y == other.y: + return self.x < other.x + return self.y < other.y + + def euclidean_distance(self, other: Point) -> float: + """ + Calculate Euclidean distance between two points. + + >>> Point(0, 0).euclidean_distance(Point(3, 4)) + 5.0 + >>> Point(1, 1).euclidean_distance(Point(4, 5)) + 5.0 + """ + return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5 + + def consecutive_orientation(self, point_a: Point, point_b: Point) -> float: + """ + Calculate the cross product of vectors (self -> point_a) and + (point_a -> point_b). + + Returns: + - Positive value: counter-clockwise turn + - Negative value: clockwise turn + - Zero: collinear points + + >>> Point(0, 0).consecutive_orientation(Point(1, 0), Point(1, 1)) + 1.0 + >>> Point(0, 0).consecutive_orientation(Point(1, 0), Point(1, -1)) + -1.0 + >>> Point(0, 0).consecutive_orientation(Point(1, 0), Point(2, 0)) + 0.0 + """ + return (point_a.x - self.x) * (point_b.y - point_a.y) - (point_a.y - self.y) * ( + point_b.x - point_a.x + ) + + +def graham_scan(points: Sequence[Point]) -> list[Point]: + """ + Find the convex hull of a set of points using the Graham scan algorithm. + + The algorithm works as follows: + 1. Find the bottom-most point (or left-most in case of tie) + 2. Sort all other points by polar angle with respect to the bottom-most point + 3. Process points in order, maintaining a stack of hull candidates + 4. Remove points that would create a clockwise turn + + Args: + points: A sequence of Point objects + + Returns: + A list of Point objects representing the convex hull in counter-clockwise order. + Returns an empty list if there are fewer than 3 distinct points or if all + points are collinear. + + Time Complexity: O(n log n) due to sorting + Space Complexity: O(n) for the output hull + + >>> graham_scan([]) + [] + >>> graham_scan([Point(0, 0)]) + [] + >>> graham_scan([Point(0, 0), Point(1, 1)]) + [] + >>> hull = graham_scan([Point(0, 0), Point(1, 0), Point(0.5, 1)]) + >>> len(hull) + 3 + >>> Point(0, 0) in hull and Point(1, 0) in hull and Point(0.5, 1) in hull + True + """ + if len(points) <= 2: + return [] + + # Find the bottom-most point (left-most in case of tie) + min_point = min(points) + + # Remove the min_point from the list + points_list = [p for p in points if p != min_point] + if not points_list: + # Edge case where all points are the same + return [] + + def polar_angle_key(point: Point) -> tuple[float, float, float]: + """ + Key function for sorting points by polar angle relative to min_point. + + Points are sorted counter-clockwise. When two points have the same angle, + the farther point comes first (we'll remove duplicates later). + """ + # We use a dummy third point (min_point itself) to calculate relative angles + # Instead, we'll compute the angle between points + dx = point.x - min_point.x + dy = point.y - min_point.y + + # Use atan2 for angle, but we can also use cross product for comparison + # For sorting, we compare orientations between consecutive points + distance = min_point.euclidean_distance(point) + return (dx, dy, -distance) # Negative distance to sort farther points first + + # Sort by polar angle using a comparison based on cross product + def compare_points(point_a: Point, point_b: Point) -> int: + """Compare two points by polar angle relative to min_point.""" + orientation = min_point.consecutive_orientation(point_a, point_b) + if orientation < 0.0: + return 1 # point_a comes after point_b (clockwise) + elif orientation > 0.0: + return -1 # point_a comes before point_b (counter-clockwise) + else: + # Collinear: farther point should come first + dist_a = min_point.euclidean_distance(point_a) + dist_b = min_point.euclidean_distance(point_b) + if dist_b < dist_a: + return -1 + elif dist_b > dist_a: + return 1 + else: + return 0 + + from functools import cmp_to_key + + points_list.sort(key=cmp_to_key(compare_points)) + + # Build the convex hull + convex_hull: list[Point] = [min_point, points_list[0]] + + for point in points_list[1:]: + # Skip consecutive points with the same angle (collinear with min_point) + if min_point.consecutive_orientation(point, convex_hull[-1]) == 0.0: + continue + + # Remove points that create a clockwise turn (or are collinear) + while len(convex_hull) >= 2: + orientation = convex_hull[-2].consecutive_orientation( + convex_hull[-1], point + ) + if orientation <= 0.0: + convex_hull.pop() + else: + break + + convex_hull.append(point) + + # Need at least 3 points for a valid convex hull + if len(convex_hull) <= 2: + return [] + + return convex_hull + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # Example usage + points = [ + Point(0, 0), + Point(1, 0), + Point(2, 0), + Point(2, 1), + Point(2, 2), + Point(1, 2), + Point(0, 2), + Point(0, 1), + Point(1, 1), # Interior point + ] + + hull = graham_scan(points) + print("Convex hull vertices:") + for point in hull: + print(f" ({point.x}, {point.y})") diff --git a/geometry/tests/test_graham_scan.py b/geometry/tests/test_graham_scan.py new file mode 100644 index 000000000000..d9a573289ce9 --- /dev/null +++ b/geometry/tests/test_graham_scan.py @@ -0,0 +1,266 @@ +""" +Tests for the Graham scan convex hull algorithm. +""" + +from geometry.graham_scan import Point, graham_scan + + +def test_empty_points() -> None: + """Test with no points.""" + assert graham_scan([]) == [] + + +def test_single_point() -> None: + """Test with a single point.""" + assert graham_scan([Point(0, 0)]) == [] + + +def test_two_points() -> None: + """Test with two points.""" + assert graham_scan([Point(0, 0), Point(1, 1)]) == [] + + +def test_duplicate_points() -> None: + """Test with all duplicate points.""" + p = Point(0, 0) + points = [p, Point(0, 0), Point(0, 0), Point(0, 0), Point(0, 0)] + assert graham_scan(points) == [] + + +def test_collinear_points() -> None: + """Test with all points on the same line.""" + points = [ + Point(1, 0), + Point(2, 0), + Point(3, 0), + Point(4, 0), + Point(5, 0), + ] + assert graham_scan(points) == [] + + +def test_triangle() -> None: + """Test with a triangle (3 points).""" + p1 = Point(1, 1) + p2 = Point(2, 1) + p3 = Point(1.5, 2) + points = [p1, p2, p3] + hull = graham_scan(points) + + assert len(hull) == 3 + assert p1 in hull + assert p2 in hull + assert p3 in hull + + +def test_rectangle() -> None: + """Test with a rectangle (4 points).""" + p1 = Point(1, 1) + p2 = Point(2, 1) + p3 = Point(2, 2) + p4 = Point(1, 2) + points = [p1, p2, p3, p4] + hull = graham_scan(points) + + assert len(hull) == 4 + assert all(p in hull for p in points) + + +def test_triangle_with_interior_points() -> None: + """Test triangle with points inside.""" + p1 = Point(1, 1) + p2 = Point(2, 1) + p3 = Point(1.5, 2) + p4 = Point(1.5, 1.5) # Interior + p5 = Point(1.2, 1.3) # Interior + p6 = Point(1.8, 1.2) # Interior + p7 = Point(1.5, 1.9) # Interior + + hull_points = [p1, p2, p3] + interior_points = [p4, p5, p6, p7] + all_points = hull_points + interior_points + + hull = graham_scan(all_points) + + # All hull points should be in the result + for p in hull_points: + assert p in hull + + # No interior points should be in the result + for p in interior_points: + assert p not in hull + + +def test_rectangle_with_interior_points() -> None: + """Test rectangle with points inside.""" + p1 = Point(1, 1) + p2 = Point(2, 1) + p3 = Point(2, 2) + p4 = Point(1, 2) + p5 = Point(1.5, 1.5) # Interior + p6 = Point(1.2, 1.3) # Interior + p7 = Point(1.8, 1.2) # Interior + p8 = Point(1.9, 1.7) # Interior + p9 = Point(1.4, 1.9) # Interior + + hull_points = [p1, p2, p3, p4] + interior_points = [p5, p6, p7, p8, p9] + all_points = hull_points + interior_points + + hull = graham_scan(all_points) + + # All hull points should be in the result + for p in hull_points: + assert p in hull + + # No interior points should be in the result + for p in interior_points: + assert p not in hull + + +def test_star_shape() -> None: + """Test with a star shape where only tips are on the convex hull.""" + # Tips of the star (on convex hull) + p1 = Point(-5, 6) + p2 = Point(-11, 0) + p3 = Point(-9, -8) + p4 = Point(4, 4) + p5 = Point(6, -7) + + # Interior points (not on convex hull) + p6 = Point(-7, -2) + p7 = Point(-2, -4) + p8 = Point(0, 1) + p9 = Point(1, 0) + p10 = Point(-6, 1) + + hull_points = [p1, p2, p3, p4, p5] + interior_points = [p6, p7, p8, p9, p10] + all_points = hull_points + interior_points + + hull = graham_scan(all_points) + + # All hull points should be in the result + for p in hull_points: + assert p in hull + + # No interior points should be in the result + for p in interior_points: + assert p not in hull + + +def test_rectangle_with_collinear_points() -> None: + """Test rectangle with points on the edges (collinear with vertices).""" + p1 = Point(1, 1) + p2 = Point(2, 1) + p3 = Point(2, 2) + p4 = Point(1, 2) + p5 = Point(1.5, 1) # On edge p1-p2 + p6 = Point(1, 1.5) # On edge p1-p4 + p7 = Point(2, 1.5) # On edge p2-p3 + p8 = Point(1.5, 2) # On edge p3-p4 + + hull_points = [p1, p2, p3, p4] + edge_points = [p5, p6, p7, p8] + all_points = hull_points + edge_points + + hull = graham_scan(all_points) + + # All corner points should be in the result + for p in hull_points: + assert p in hull + + # Edge points should not be in the result (only corners) + for p in edge_points: + assert p not in hull + + +def test_point_equality() -> None: + """Test Point equality.""" + p1 = Point(1, 2) + p2 = Point(1, 2) + p3 = Point(2, 1) + + assert p1 == p2 + assert p1 != p3 + + +def test_point_comparison() -> None: + """Test Point comparison for sorting.""" + p1 = Point(1, 2) + p2 = Point(1, 3) + p3 = Point(2, 2) + + assert p1 < p2 # Lower y value + assert p1 < p3 # Same y, lower x + assert not p2 < p1 + + +def test_euclidean_distance() -> None: + """Test Euclidean distance calculation.""" + p1 = Point(0, 0) + p2 = Point(3, 4) + + assert p1.euclidean_distance(p2) == 5.0 + + +def test_consecutive_orientation() -> None: + """Test orientation calculation.""" + p1 = Point(0, 0) + p2 = Point(1, 0) + p3_ccw = Point(1, 1) # Counter-clockwise + p3_cw = Point(1, -1) # Clockwise + p3_collinear = Point(2, 0) # Collinear + + assert p1.consecutive_orientation(p2, p3_ccw) > 0 # Counter-clockwise + assert p1.consecutive_orientation(p2, p3_cw) < 0 # Clockwise + assert p1.consecutive_orientation(p2, p3_collinear) == 0 # Collinear + + +def test_large_hull() -> None: + """Test with a larger set of points.""" + # Create a circle of points + import math + + points = [] + for i in range(20): + angle = 2 * math.pi * i / 20 + x = math.cos(angle) + y = math.sin(angle) + points.append(Point(x, y)) + + # Add some interior points + points.append(Point(0, 0)) + points.append(Point(0.5, 0.5)) + points.append(Point(-0.3, 0.2)) + + hull = graham_scan(points) + + # The hull should contain the circle points but not the interior points + assert len(hull) >= 3 + assert Point(0, 0) not in hull + assert Point(0.5, 0.5) not in hull + assert Point(-0.3, 0.2) not in hull + + +def test_random_order() -> None: + """Test that point order doesn't affect the result.""" + p1 = Point(0, 0) + p2 = Point(4, 0) + p3 = Point(4, 3) + p4 = Point(0, 3) + p5 = Point(2, 1.5) # Interior + + # Try different orderings + order1 = [p1, p2, p3, p4, p5] + order2 = [p5, p4, p3, p2, p1] + order3 = [p3, p5, p1, p4, p2] + + hull1 = graham_scan(order1) + hull2 = graham_scan(order2) + hull3 = graham_scan(order3) + + # All should have the same points (though possibly in different order) + assert len(hull1) == len(hull2) == len(hull3) == 4 + assert {(p.x, p.y) for p in hull1} == {(p.x, p.y) for p in hull2} + assert {(p.x, p.y) for p in hull2} == {(p.x, p.y) for p in hull3} From 8e70e2e77bc534d09f655410fd1a8a38d2404928 Mon Sep 17 00:00:00 2001 From: zain-cs Date: Mon, 9 Mar 2026 07:40:39 +0500 Subject: [PATCH 10/38] Add type hints and improve generate_parentheses_iterative (#14324) * Add type hints and improve code quality for generate_parentheses_iterative * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix line length issue * Update generate_parentheses_iterative.py * Update generate_parentheses_iterative.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- backtracking/generate_parentheses_iterative.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backtracking/generate_parentheses_iterative.py b/backtracking/generate_parentheses_iterative.py index 175941c7ae95..84c032f52dc4 100644 --- a/backtracking/generate_parentheses_iterative.py +++ b/backtracking/generate_parentheses_iterative.py @@ -1,11 +1,12 @@ -def generate_parentheses_iterative(length: int) -> list: +def generate_parentheses_iterative(length: int) -> list[str]: """ Generate all valid combinations of parentheses (Iterative Approach). The algorithm works as follows: 1. Initialize an empty list to store the combinations. 2. Initialize a stack to keep track of partial combinations. - 3. Start with empty string and push it onstack along with the counts of '(' and ')'. + 3. Start with empty string and push it on stack along with + the counts of '(' and ')'. 4. While the stack is not empty: a. Pop a partial combination and its open and close counts from the stack. b. If the combination length is equal to 2*length, add it to the result. @@ -34,8 +35,11 @@ def generate_parentheses_iterative(length: int) -> list: >>> generate_parentheses_iterative(0) [''] """ - result = [] - stack = [] + if length == 0: + return [""] + + result: list[str] = [] + stack: list[tuple[str, int, int]] = [] # Each element in stack is a tuple (current_combination, open_count, close_count) stack.append(("", 0, 0)) @@ -45,6 +49,7 @@ def generate_parentheses_iterative(length: int) -> list: if len(current_combination) == 2 * length: result.append(current_combination) + continue if open_count < length: stack.append((current_combination + "(", open_count + 1, close_count)) From 0324e6098d1e44e52b9a12efd64fb72264fef513 Mon Sep 17 00:00:00 2001 From: umutKaracelebi Date: Mon, 9 Mar 2026 06:18:15 +0300 Subject: [PATCH 11/38] style: add type hints to matrix_exponentiation.py (#14288) * style: add type hints to matrix_exponentiation.py * Refactor kth_permutation and fix linter errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete machine_learning/linear_discriminant_analysis.py * Revert "Delete machine_learning/linear_discriminant_analysis.py" This reverts commit de2964731113d240db9d4e5451e2a51b48cc6ace. * Update linear_discriminant_analysis.py * Update kth_lexicographic_permutation.py * Update matrix_exponentiation.py * Update matrix_exponentiation.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/matrix_exponentiation.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py index 7cdac9d34674..15b0c96e0f07 100644 --- a/maths/matrix_exponentiation.py +++ b/maths/matrix_exponentiation.py @@ -11,7 +11,7 @@ class Matrix: - def __init__(self, arg): + def __init__(self, arg: list[list] | int) -> None: if isinstance(arg, list): # Initializes a matrix identical to the one provided. self.t = arg self.n = len(arg) @@ -19,7 +19,7 @@ def __init__(self, arg): self.n = arg self.t = [[0 for _ in range(self.n)] for _ in range(self.n)] - def __mul__(self, b): + def __mul__(self, b: Matrix) -> Matrix: matrix = Matrix(self.n) for i in range(self.n): for j in range(self.n): @@ -28,7 +28,7 @@ def __mul__(self, b): return matrix -def modular_exponentiation(a, b): +def modular_exponentiation(a: Matrix, b: int) -> Matrix: matrix = Matrix([[1, 0], [0, 1]]) while b > 0: if b & 1: @@ -38,7 +38,7 @@ def modular_exponentiation(a, b): return matrix -def fibonacci_with_matrix_exponentiation(n, f1, f2): +def fibonacci_with_matrix_exponentiation(n: int, f1: int, f2: int) -> int: """ Returns the nth number of the Fibonacci sequence that starts with f1 and f2 @@ -64,7 +64,7 @@ def fibonacci_with_matrix_exponentiation(n, f1, f2): return f2 * matrix.t[0][0] + f1 * matrix.t[0][1] -def simple_fibonacci(n, f1, f2): +def simple_fibonacci(n: int, f1: int, f2: int) -> int: """ Returns the nth number of the Fibonacci sequence that starts with f1 and f2 @@ -95,7 +95,7 @@ def simple_fibonacci(n, f1, f2): return f2 -def matrix_exponentiation_time(): +def matrix_exponentiation_time() -> float: setup = """ from random import randint from __main__ import fibonacci_with_matrix_exponentiation @@ -106,7 +106,7 @@ def matrix_exponentiation_time(): return exec_time -def simple_fibonacci_time(): +def simple_fibonacci_time() -> float: setup = """ from random import randint from __main__ import simple_fibonacci @@ -119,7 +119,7 @@ def simple_fibonacci_time(): return exec_time -def main(): +def main() -> None: matrix_exponentiation_time() simple_fibonacci_time() From 81fcb90f7b7f88bb4b9bbc1f85967b5f26840fda Mon Sep 17 00:00:00 2001 From: Aarav Arya <159852168+Aarav-Arya@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:32:52 -0400 Subject: [PATCH 12/38] Add type hints for bogo_sort.py (#14306) * Added type hints for bogo_sort * Update bogo_sort.py --------- Co-authored-by: Maxim Smolskiy --- sorts/bogo_sort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sorts/bogo_sort.py b/sorts/bogo_sort.py index 9c133f0d8a55..70785140ee5c 100644 --- a/sorts/bogo_sort.py +++ b/sorts/bogo_sort.py @@ -16,7 +16,7 @@ import random -def bogo_sort(collection): +def bogo_sort(collection: list) -> list: """Pure implementation of the bogosort algorithm in Python :param collection: some mutable ordered collection with heterogeneous comparable items inside @@ -30,7 +30,7 @@ def bogo_sort(collection): [-45, -5, -2] """ - def is_sorted(collection): + def is_sorted(collection: list) -> bool: for i in range(len(collection) - 1): if collection[i] > collection[i + 1]: return False From 84b59c878186b7ed9e713f7409ccbf0473e3bc0c Mon Sep 17 00:00:00 2001 From: Yaadhuu Date: Mon, 9 Mar 2026 09:38:51 +0530 Subject: [PATCH 13/38] Handle gcd(0, 0) edge case (#14215) * Use TypeError for non-string input in count_vowels * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix docstring and improve input validation in kth_lexicographic_permutation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Handle gcd(0, 0) edge case * Update kth_lexicographic_permutation.py * Update count_vowels.py * Update greatest_common_divisor.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/greatest_common_divisor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index a2174a8eb74a..1fc123fc2b14 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -30,6 +30,8 @@ def greatest_common_divisor(a: int, b: int) -> int: 3 >>> greatest_common_divisor(-3, -9) 3 + >>> greatest_common_divisor(0, 0) + 0 """ return abs(b) if a == 0 else greatest_common_divisor(b % a, a) @@ -50,6 +52,8 @@ def gcd_by_iterative(x: int, y: int) -> int: 1 >>> gcd_by_iterative(11, 37) 1 + >>> gcd_by_iterative(0, 0) + 0 """ while y: # --> when y=0 then loop will terminate and return x as final GCD. x, y = y, x % y From 6f9f4318af4e93a0f7eaa4a2c3dfe698e0d31f6f Mon Sep 17 00:00:00 2001 From: Kalyani Date: Mon, 9 Mar 2026 09:45:03 +0530 Subject: [PATCH 14/38] all "is" replaced with "==" (#14209) Co-authored-by: Maxim Smolskiy --- strings/palindrome.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/strings/palindrome.py b/strings/palindrome.py index e765207e5942..4df5639b0c49 100644 --- a/strings/palindrome.py +++ b/strings/palindrome.py @@ -15,14 +15,14 @@ "AB": False, } # Ensure our test data is valid -assert all((key == key[::-1]) is value for key, value in test_data.items()) +assert all((key == key[::-1]) == value for key, value in test_data.items()) def is_palindrome(s: str) -> bool: """ Return True if s is a palindrome otherwise return False. - >>> all(is_palindrome(key) is value for key, value in test_data.items()) + >>> all(is_palindrome(key) == value for key, value in test_data.items()) True """ @@ -41,7 +41,7 @@ def is_palindrome_traversal(s: str) -> bool: """ Return True if s is a palindrome otherwise return False. - >>> all(is_palindrome_traversal(key) is value for key, value in test_data.items()) + >>> all(is_palindrome_traversal(key) == value for key, value in test_data.items()) True """ end = len(s) // 2 @@ -60,7 +60,7 @@ def is_palindrome_recursive(s: str) -> bool: """ Return True if s is a palindrome otherwise return False. - >>> all(is_palindrome_recursive(key) is value for key, value in test_data.items()) + >>> all(is_palindrome_recursive(key) == value for key, value in test_data.items()) True """ if len(s) <= 1: @@ -75,14 +75,14 @@ def is_palindrome_slice(s: str) -> bool: """ Return True if s is a palindrome otherwise return False. - >>> all(is_palindrome_slice(key) is value for key, value in test_data.items()) + >>> all(is_palindrome_slice(key) == value for key, value in test_data.items()) True """ return s == s[::-1] def benchmark_function(name: str) -> None: - stmt = f"all({name}(key) is value for key, value in test_data.items())" + stmt = f"all({name}(key) == value for key, value in test_data.items())" setup = f"from __main__ import test_data, {name}" number = 500000 result = timeit(stmt=stmt, setup=setup, number=number) @@ -91,8 +91,8 @@ def benchmark_function(name: str) -> None: if __name__ == "__main__": for key, value in test_data.items(): - assert is_palindrome(key) is is_palindrome_recursive(key) - assert is_palindrome(key) is is_palindrome_slice(key) + assert is_palindrome(key) == is_palindrome_recursive(key) + assert is_palindrome(key) == is_palindrome_slice(key) print(f"{key:21} {value}") print("a man a plan a canal panama") From 049a34d62be6abe5ce5dcef9b7c75580c6fd3e8f Mon Sep 17 00:00:00 2001 From: Tithi Joshi Date: Mon, 9 Mar 2026 10:00:39 +0530 Subject: [PATCH 15/38] Refine docstring and simplify reverse_letters implementation (#14205) * docs: refine docstring and simplify reverse_letters implementation Updated the docstring for clarity and improved the logic for reversing words. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update reverse_letters.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- strings/reverse_letters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/strings/reverse_letters.py b/strings/reverse_letters.py index 4f73f816b382..cd1b7832d066 100644 --- a/strings/reverse_letters.py +++ b/strings/reverse_letters.py @@ -1,7 +1,7 @@ def reverse_letters(sentence: str, length: int = 0) -> str: """ Reverse all words that are longer than the given length of characters in a sentence. - If unspecified, length is taken as 0 + If ``length`` is not specified, it defaults to 0. >>> reverse_letters("Hey wollef sroirraw", 3) 'Hey fellow warriors' @@ -13,7 +13,7 @@ def reverse_letters(sentence: str, length: int = 0) -> str: 'racecar' """ return " ".join( - "".join(word[::-1]) if len(word) > length else word for word in sentence.split() + word[::-1] if len(word) > length else word for word in sentence.split() ) From f5c3e7c80821512ee2d47a891b9636d436e76705 Mon Sep 17 00:00:00 2001 From: Anusha <135559258+Anusha-DeviE@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:07:31 +0530 Subject: [PATCH 16/38] Replace assert-based validation with explicit errors in modular_division (#14204) * Improve documentation for linear search algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replace asserts with explicit validation in modular_division * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update linear_search.py --------- Co-authored-by: Anusha-DeviE Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/modular_division.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/maths/modular_division.py b/maths/modular_division.py index 94f12b3e096e..ed4ae6ae8ce3 100644 --- a/maths/modular_division.py +++ b/maths/modular_division.py @@ -28,9 +28,13 @@ def modular_division(a: int, b: int, n: int) -> int: 4 """ - assert n > 1 - assert a > 0 - assert greatest_common_divisor(a, n) == 1 + if n <= 1: + raise ValueError("Modulus n must be greater than 1") + if a <= 0: + raise ValueError("Divisor a must be a positive integer") + if greatest_common_divisor(a, n) != 1: + raise ValueError("a and n must be coprime (gcd(a, n) = 1)") + (_d, _t, s) = extended_gcd(n, a) # Implemented below x = (b * s) % n return x From 1ae906a97bd1245107d61bba06558f9a35222b91 Mon Sep 17 00:00:00 2001 From: Shivang Arya Date: Mon, 9 Mar 2026 10:19:15 +0530 Subject: [PATCH 17/38] Fix incorrect doctest references in fibonacci functions (#14200) * Fix incorrect doctest references in fibonacci functions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- maths/fibonacci.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 71ff479f9cc2..595233cf8446 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -91,15 +91,15 @@ def fib_iterative(n: int) -> list[int]: def fib_recursive(n: int) -> list[int]: """ Calculates the first n (0-indexed) Fibonacci numbers using recursion - >>> fib_iterative(0) + >>> fib_recursive(0) [0] - >>> fib_iterative(1) + >>> fib_recursive(1) [0, 1] - >>> fib_iterative(5) + >>> fib_recursive(5) [0, 1, 1, 2, 3, 5] - >>> fib_iterative(10) + >>> fib_recursive(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - >>> fib_iterative(-1) + >>> fib_recursive(-1) Traceback (most recent call last): ... ValueError: n is negative @@ -119,7 +119,7 @@ def fib_recursive_term(i: int) -> int: >>> fib_recursive_term(-1) Traceback (most recent call last): ... - Exception: n is negative + ValueError: n is negative """ if i < 0: raise ValueError("n is negative") @@ -135,15 +135,15 @@ def fib_recursive_term(i: int) -> int: def fib_recursive_cached(n: int) -> list[int]: """ Calculates the first n (0-indexed) Fibonacci numbers using recursion - >>> fib_iterative(0) + >>> fib_recursive_cached(0) [0] - >>> fib_iterative(1) + >>> fib_recursive_cached(1) [0, 1] - >>> fib_iterative(5) + >>> fib_recursive_cached(5) [0, 1, 1, 2, 3, 5] - >>> fib_iterative(10) + >>> fib_recursive_cached(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - >>> fib_iterative(-1) + >>> fib_recursive_cached(-1) Traceback (most recent call last): ... ValueError: n is negative @@ -176,7 +176,7 @@ def fib_memoization(n: int) -> list[int]: [0, 1, 1, 2, 3, 5] >>> fib_memoization(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - >>> fib_iterative(-1) + >>> fib_memoization(-1) Traceback (most recent call last): ... ValueError: n is negative From da6b9e9687148f6f99810f761ac59e45b5c1031c Mon Sep 17 00:00:00 2001 From: Yaadhuu Date: Mon, 9 Mar 2026 10:27:20 +0530 Subject: [PATCH 18/38] Use TypeError for non-string input in count_vowels (#14196) * Use TypeError for non-string input in count_vowels * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix docstring and improve input validation in kth_lexicographic_permutation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update kth_lexicographic_permutation.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- strings/count_vowels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/count_vowels.py b/strings/count_vowels.py index 8a52b331c81b..e222d80590ff 100644 --- a/strings/count_vowels.py +++ b/strings/count_vowels.py @@ -22,7 +22,7 @@ def count_vowels(s: str) -> int: 1 """ if not isinstance(s, str): - raise ValueError("Input must be a string") + raise TypeError("Input must be a string") vowels = "aeiouAEIOU" return sum(1 for char in s if char in vowels) From 71c7fc8eed7f119a6749fe65efba15ef63ce2c8f Mon Sep 17 00:00:00 2001 From: JAVED KHAN AHMED <149111497+JavedKhan93@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:39:38 +0530 Subject: [PATCH 19/38] docs: upgrade mypy link to https (#14184) Co-authored-by: JavedKhan93 Co-authored-by: Maxim Smolskiy --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 35de0bf75ed5..aa6bff3ad1da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -159,7 +159,7 @@ We want your work to be readable by others; therefore, we encourage you to note starting_value = int(input("Please enter a starting value: ").strip()) ``` - The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](http://mypy-lang.org) so run that locally before making your submission. + The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](https://mypy-lang.org) so run that locally before making your submission. ```python def sum_ab(a: int, b: int) -> int: From 6da02abdce243c947688ddaf1926c9aacb0cbb7b Mon Sep 17 00:00:00 2001 From: radhikaRM06 Date: Mon, 9 Mar 2026 10:52:38 +0530 Subject: [PATCH 20/38] Add doctests for duplicate and sorted inputs in bubble sort (#14154) Co-authored-by: Maxim Smolskiy --- sorts/bubble_sort.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 9ec3d5384f38..b09a2afed928 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -17,6 +17,12 @@ def bubble_sort_iterative(collection: list[Any]) -> list[Any]: [-45, -5, -2] >>> bubble_sort_iterative([-23, 0, 6, -4, 34]) [-23, -4, 0, 6, 34] + >>> bubble_sort_iterative([1, 2, 3, 4]) + [1, 2, 3, 4] + >>> bubble_sort_iterative([3, 3, 3, 3]) + [3, 3, 3, 3] + >>> bubble_sort_iterative([56]) + [56] >>> bubble_sort_iterative([0, 5, 2, 3, 2]) == sorted([0, 5, 2, 3, 2]) True >>> bubble_sort_iterative([]) == sorted([]) From 589d12972d185872b955633f9e7879fb6ecc4317 Mon Sep 17 00:00:00 2001 From: Parth Pawar Date: Mon, 9 Mar 2026 11:03:34 +0530 Subject: [PATCH 21/38] Fix return type description in bubble_sort.py (#14137) * Fix return type description in bubble_sort.py Better (clearer, standard phrasing): :return: the same collection ordered in ascending order * Update bubble_sort.py --------- Co-authored-by: Maxim Smolskiy --- sorts/bubble_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index b09a2afed928..77d173290aff 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -6,7 +6,7 @@ def bubble_sort_iterative(collection: list[Any]) -> list[Any]: :param collection: some mutable ordered collection with heterogeneous comparable items inside - :return: the same collection ordered by ascending + :return: the same collection ordered in ascending order Examples: >>> bubble_sort_iterative([0, 5, 2, 3, 2]) From 7af5aba250cd9b382de5b489e0e571f08f4a4416 Mon Sep 17 00:00:00 2001 From: Yaswanth Naga Sai K <140506928+YASWANTH1976@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:25:03 +0530 Subject: [PATCH 22/38] Improve sorted input validation in binary search (#14074) * Improve sorted input validation in binary search * Update binary_search.py * Update binary_search.py * Update binary_search.py --------- Co-authored-by: Maxim Smolskiy --- searches/binary_search.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 5125dc6bdb9a..bec87b3c5aec 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -10,9 +10,8 @@ python3 binary_search.py """ -from __future__ import annotations - import bisect +from itertools import pairwise def bisect_left( @@ -198,7 +197,7 @@ def binary_search(sorted_collection: list[int], item: int) -> int: >>> binary_search([0, 5, 7, 10, 15], 6) -1 """ - if list(sorted_collection) != sorted(sorted_collection): + if any(a > b for a, b in pairwise(sorted_collection)): raise ValueError("sorted_collection must be sorted in ascending order") left = 0 right = len(sorted_collection) - 1 From 9ea690e098c357fa3459a42ca2c2d5f71530b000 Mon Sep 17 00:00:00 2001 From: Yaswanth Naga Sai K <140506928+YASWANTH1976@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:35:13 +0530 Subject: [PATCH 23/38] Improve grammar in linear_search docstring (#14081) * Improve grammar in linear_search docstring Improved wording in the linear search docstring for better clarity. No code logic changed. * Update linear_search.py * Update linear_search.py --------- Co-authored-by: Maxim Smolskiy --- searches/linear_search.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/searches/linear_search.py b/searches/linear_search.py index ba6e81d6bae4..8adb4a7015f0 100644 --- a/searches/linear_search.py +++ b/searches/linear_search.py @@ -1,5 +1,5 @@ """ -This is pure Python implementation of linear search algorithm +This is a pure Python implementation of the linear search algorithm. For doctests run following command: python3 -m doctest -v linear_search.py @@ -12,8 +12,8 @@ def linear_search(sequence: list, target: int) -> int: """A pure Python implementation of a linear search algorithm - :param sequence: a collection with comparable items (as sorted items not required - in Linear Search) + :param sequence: a collection with comparable items (sorting is not required for + linear search) :param target: item value to search :return: index of found item or -1 if item is not found From 32a3d0d0bb5d9b85a2642c82275185281b061878 Mon Sep 17 00:00:00 2001 From: ADDALA MATHEW Date: Mon, 9 Mar 2026 22:58:01 +0530 Subject: [PATCH 24/38] Fix doctest bug in bubble_sort_recursive - incorrect function call (#13821) Fixed bug in bubble_sort_recursive docstring where the doctest was calling bubble_sort_iterative([]) instead of bubble_sort_recursive([]). This was a copy-paste error that would cause the doctest to pass even if bubble_sort_recursive had issues with empty lists. Change: - Line 71: Changed '>>> bubble_sort_iterative([])' to '>>> bubble_sort_recursive([])' This ensures the doctest properly validates the recursive implementation. Co-authored-by: Maxim Smolskiy --- sorts/bubble_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py index 77d173290aff..4d658a4a12e4 100644 --- a/sorts/bubble_sort.py +++ b/sorts/bubble_sort.py @@ -69,7 +69,7 @@ def bubble_sort_recursive(collection: list[Any]) -> list[Any]: Examples: >>> bubble_sort_recursive([0, 5, 2, 3, 2]) [0, 2, 2, 3, 5] - >>> bubble_sort_iterative([]) + >>> bubble_sort_recursive([]) [] >>> bubble_sort_recursive([-2, -45, -5]) [-45, -5, -2] From af131b7c3f9311607878565acd49e3e3002c294c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Fern=C3=A1ndez=20Gonz=C3=A1lez?= Date: Tue, 10 Mar 2026 06:44:09 +0100 Subject: [PATCH 25/38] Remove duplicated return statement in area_reg_polygon (#14362) --- maths/area.py | 1 - 1 file changed, 1 deletion(-) diff --git a/maths/area.py b/maths/area.py index 31a654206977..e14cc0aa7195 100644 --- a/maths/area.py +++ b/maths/area.py @@ -552,7 +552,6 @@ def area_reg_polygon(sides: int, length: float) -> float: length of a side" ) return (sides * length**2) / (4 * tan(pi / sides)) - return (sides * length**2) / (4 * tan(pi / sides)) if __name__ == "__main__": From 135c748e55a415f5c61af49024b688dd8708bf83 Mon Sep 17 00:00:00 2001 From: Tejas Rahane <161036451+Tejasrahane@users.noreply.github.com> Date: Wed, 11 Mar 2026 13:26:30 +0530 Subject: [PATCH 26/38] Fix doctests in factorial_recursive function (#13703) * Fix doctests in factorial_recursive function The doctests in factorial_recursive were calling factorial() instead of factorial_recursive(). This fix ensures that the tests correctly validate the factorial_recursive function itself. * Update factorial.py --------- Co-authored-by: Maxim Smolskiy --- maths/factorial.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/maths/factorial.py b/maths/factorial.py index ba61447c7564..2b8b68764d89 100644 --- a/maths/factorial.py +++ b/maths/factorial.py @@ -41,21 +41,21 @@ def factorial_recursive(n: int) -> int: https://en.wikipedia.org/wiki/Factorial >>> import math - >>> all(factorial(i) == math.factorial(i) for i in range(20)) + >>> all(factorial_recursive(i) == math.factorial(i) for i in range(20)) True - >>> factorial(0.1) + >>> factorial_recursive(0.1) Traceback (most recent call last): ... - ValueError: factorial() only accepts integral values - >>> factorial(-1) + ValueError: factorial_recursive() only accepts integral values + >>> factorial_recursive(-1) Traceback (most recent call last): ... - ValueError: factorial() not defined for negative values + ValueError: factorial_recursive() not defined for negative values """ if not isinstance(n, int): - raise ValueError("factorial() only accepts integral values") + raise ValueError("factorial_recursive() only accepts integral values") if n < 0: - raise ValueError("factorial() not defined for negative values") + raise ValueError("factorial_recursive() not defined for negative values") return 1 if n in {0, 1} else n * factorial_recursive(n - 1) From c34f23e47e53cd5068448039eba5748f0f6a80d3 Mon Sep 17 00:00:00 2001 From: Thrive <147845726+VAIBHAVVARORA@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:30:21 +0530 Subject: [PATCH 27/38] Add Extra edge cases (#12995) * Add Extra edge cases * Update coloring.py --------- Co-authored-by: Maxim Smolskiy --- backtracking/coloring.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backtracking/coloring.py b/backtracking/coloring.py index f10cdbcf9d26..abfdf16f1342 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -104,6 +104,14 @@ def color(graph: list[list[int]], max_colors: int) -> list[int]: >>> max_colors = 2 >>> color(graph, max_colors) [] + >>> color([], 2) # empty graph + [] + >>> color([[0]], 1) # single node, 1 color + [0] + >>> color([[0, 1], [1, 0]], 1) # 2 nodes, 1 color (impossible) + [] + >>> color([[0, 1], [1, 0]], 2) # 2 nodes, 2 colors (possible) + [0, 1] """ colored_vertices = [-1] * len(graph) From a2efba59bfb0dd5920cfa05572fc7843ea454d28 Mon Sep 17 00:00:00 2001 From: Mindaugas <76015221+mindaugl@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:00:28 +0200 Subject: [PATCH 28/38] Add euler project problem 15 additional solution (#12774) * Add euler project problem 15 additional solution by explicitly counting the paths. * Update sol2.py * updating DIRECTORY.md * updating DIRECTORY.md * Trigger CI * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Maxim Smolskiy Co-authored-by: MaximSmolskiy Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- DIRECTORY.md | 6 ++++++ project_euler/problem_015/sol2.py | 32 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 project_euler/problem_015/sol2.py diff --git a/DIRECTORY.md b/DIRECTORY.md index a73c630bc8a7..ca454bd5fd82 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -469,6 +469,11 @@ ## Geometry * [Geometry](geometry/geometry.py) + * [Graham Scan](geometry/graham_scan.py) + * [Jarvis March](geometry/jarvis_march.py) + * Tests + * [Test Graham Scan](geometry/tests/test_graham_scan.py) + * [Test Jarvis March](geometry/tests/test_jarvis_march.py) ## Graphics * [Bezier Curve](graphics/bezier_curve.py) @@ -981,6 +986,7 @@ * [Sol2](project_euler/problem_014/sol2.py) * Problem 015 * [Sol1](project_euler/problem_015/sol1.py) + * [Sol2](project_euler/problem_015/sol2.py) * Problem 016 * [Sol1](project_euler/problem_016/sol1.py) * [Sol2](project_euler/problem_016/sol2.py) diff --git a/project_euler/problem_015/sol2.py b/project_euler/problem_015/sol2.py new file mode 100644 index 000000000000..903095e144ec --- /dev/null +++ b/project_euler/problem_015/sol2.py @@ -0,0 +1,32 @@ +""" +Problem 15: https://projecteuler.net/problem=15 + +Starting in the top left corner of a 2x2 grid, and only being able to move to +the right and down, there are exactly 6 routes to the bottom right corner. +How many such routes are there through a 20x20 grid? +""" + + +def solution(n: int = 20) -> int: + """ + Solve by explicitly counting the paths with dynamic programming. + + >>> solution(6) + 924 + >>> solution(2) + 6 + >>> solution(1) + 2 + """ + + counts = [[1 for _ in range(n + 1)] for _ in range(n + 1)] + + for i in range(1, n + 1): + for j in range(1, n + 1): + counts[i][j] = counts[i - 1][j] + counts[i][j - 1] + + return counts[n][n] + + +if __name__ == "__main__": + print(solution()) From 7e4b60b05cb0378118e866bd7acbcde2286528af Mon Sep 17 00:00:00 2001 From: Mozart Maia <37476677+mozart-maia@users.noreply.github.com> Date: Fri, 13 Mar 2026 01:40:13 -0300 Subject: [PATCH 29/38] add some doctests to algos in backtracking (#11911) * add some doctests to algos in backtracking * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- backtracking/generate_parentheses.py | 4 ++++ backtracking/n_queens.py | 8 ++++++++ backtracking/word_break.py | 3 +++ 3 files changed, 15 insertions(+) diff --git a/backtracking/generate_parentheses.py b/backtracking/generate_parentheses.py index 18c21e2a9b51..5094f4b08619 100644 --- a/backtracking/generate_parentheses.py +++ b/backtracking/generate_parentheses.py @@ -64,6 +64,10 @@ def generate_parenthesis(n: int) -> list[str]: Example 2: >>> generate_parenthesis(1) ['()'] + + Example 3: + >>> generate_parenthesis(0) + [''] """ result: list[str] = [] diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index d10181f319b3..6fac93aa77d6 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -33,6 +33,14 @@ def is_safe(board: list[list[int]], row: int, column: int) -> bool: False >>> is_safe([[0, 0, 1], [0, 0, 0], [0, 0, 0]], 1, 1) False + >>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 2) + True + >>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 2, 1) + True + >>> is_safe([[0, 0, 0], [1, 0, 0], [0, 0, 0]], 0, 2) + True + >>> is_safe([[0, 0, 0], [1, 0, 0], [0, 0, 0]], 2, 2) + True """ n = len(board) # Size of the board diff --git a/backtracking/word_break.py b/backtracking/word_break.py index 1f2ab073f499..2e874a02b61c 100644 --- a/backtracking/word_break.py +++ b/backtracking/word_break.py @@ -66,6 +66,9 @@ def word_break(input_string: str, word_dict: set[str]) -> bool: >>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"}) False + + >>> word_break("applepenapple", {}) + False """ return backtrack(input_string, word_dict, 0) From 68473afc4b2207967453842553875aba6b9c7123 Mon Sep 17 00:00:00 2001 From: Jose Nelson Date: Fri, 13 Mar 2026 14:53:47 -0700 Subject: [PATCH 30/38] Add latitude and longitude validation to lamberts_ellipsoidal_distance (#14373) * Add latitude and longitude validation with doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update lamberts_ellipsoidal_distance.py --------- Co-authored-by: Pamela Cano Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- geodesy/lamberts_ellipsoidal_distance.py | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/geodesy/lamberts_ellipsoidal_distance.py b/geodesy/lamberts_ellipsoidal_distance.py index 4805674e51ab..a5c43c5656e9 100644 --- a/geodesy/lamberts_ellipsoidal_distance.py +++ b/geodesy/lamberts_ellipsoidal_distance.py @@ -32,6 +32,26 @@ def lamberts_ellipsoidal_distance( Returns: geographical distance between two points in metres + >>> lamberts_ellipsoidal_distance(100, 0, 0, 0) + Traceback (most recent call last): + ... + ValueError: Latitude must be between -90 and 90 degrees + + >>> lamberts_ellipsoidal_distance(0, 0, -100, 0) + Traceback (most recent call last): + ... + ValueError: Latitude must be between -90 and 90 degrees + + >>> lamberts_ellipsoidal_distance(0, 200, 0, 0) + Traceback (most recent call last): + ... + ValueError: Longitude must be between -180 and 180 degrees + + >>> lamberts_ellipsoidal_distance(0, 0, 0, -200) + Traceback (most recent call last): + ... + ValueError: Longitude must be between -180 and 180 degrees + >>> from collections import namedtuple >>> point_2d = namedtuple("point_2d", "lat lon") >>> SAN_FRANCISCO = point_2d(37.774856, -122.424227) @@ -46,6 +66,14 @@ def lamberts_ellipsoidal_distance( '9,737,326 meters' """ + # Validate latitude values + if not -90 <= lat1 <= 90 or not -90 <= lat2 <= 90: + raise ValueError("Latitude must be between -90 and 90 degrees") + + # Validate longitude values + if not -180 <= lon1 <= 180 or not -180 <= lon2 <= 180: + raise ValueError("Longitude must be between -180 and 180 degrees") + # CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System # Distance in metres(m) # Equation Parameters From 257400487e3e5da24d9e1b7c8b696a42acb18c82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:51:17 +0100 Subject: [PATCH 31/38] Bump actions/deploy-pages from 4 to 5 (#14445) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4 to 5. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sphinx.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index bf0a74a239c8..c5707804b1fa 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -53,5 +53,5 @@ jobs: needs: build_docs runs-on: ubuntu-latest steps: - - uses: actions/deploy-pages@v4 + - uses: actions/deploy-pages@v5 id: deployment From 841e947e6865cd097db6bd023dfd6171c41d7dc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:00:33 +0100 Subject: [PATCH 32/38] Bump actions/configure-pages from 5 to 6 (#14470) Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 5 to 6. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sphinx.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index c5707804b1fa..5f5a90ce1a2d 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -39,7 +39,7 @@ jobs: python-version: 3.14 allow-prereleases: true - run: uv sync --group=docs - - uses: actions/configure-pages@v5 + - uses: actions/configure-pages@v6 - run: uv run sphinx-build -c docs . docs/_build/html - uses: actions/upload-pages-artifact@v4 with: From 840ca00ad389a947205a4a928171499f2228cbb7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:50:30 +0200 Subject: [PATCH 33/38] [pre-commit.ci] pre-commit autoupdate (#14325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.14 → v0.15.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.14...v0.15.4) - [github.com/tox-dev/pyproject-fmt: v2.12.1 → v2.16.2](https://github.com/tox-dev/pyproject-fmt/compare/v2.12.1...v2.16.2) - [github.com/abravalheri/validate-pyproject: v0.24.1 → v0.25](https://github.com/abravalheri/validate-pyproject/compare/v0.24.1...v0.25) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updating DIRECTORY.md * Configure mypy to target Python 3.14 and update to v1.20.0 Agent-Logs-Url: https://github.com/TheAlgorithms/Python/sessions/332481be-4c5f-4412-abf8-6ef4a3913828 Co-authored-by: cclauss <3709715+cclauss@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy Co-authored-by: MaximSmolskiy Co-authored-by: Christian Clauss Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: cclauss <3709715+cclauss@users.noreply.github.com> --- .pre-commit-config.yaml | 24 ++++++------ .../filters/local_binary_pattern.py | 2 +- divide_and_conquer/convex_hull.py | 2 +- dynamic_programming/catalan_numbers.py | 2 +- maths/greatest_common_divisor.py | 2 +- project_euler/problem_002/sol4.py | 2 +- project_euler/problem_003/sol1.py | 2 +- project_euler/problem_003/sol2.py | 2 +- project_euler/problem_003/sol3.py | 2 +- project_euler/problem_005/sol1.py | 2 +- project_euler/problem_007/sol2.py | 2 +- pyproject.toml | 38 ++++++++++--------- web_programming/fetch_well_rx_price.py | 2 +- web_programming/instagram_crawler.py | 2 +- 14 files changed, 44 insertions(+), 42 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 765d5cff38d8..1e352f7dcb48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.14 + rev: v0.15.4 hooks: - id: ruff-check - id: ruff-format @@ -32,7 +32,7 @@ repos: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.12.1 + rev: v2.16.2 hooks: - id: pyproject-fmt @@ -45,19 +45,19 @@ repos: pass_filenames: false - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.24.1 + rev: v0.25 hooks: - id: validate-pyproject - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 - hooks: - - id: mypy - args: - - --explicit-package-bases - - --ignore-missing-imports - - --install-types - - --non-interactive + # - repo: https://github.com/pre-commit/mirrors-mypy + # rev: v1.20.0 + # hooks: + # - id: mypy + # args: + # - --explicit-package-bases + # - --ignore-missing-imports + # - --install-types + # - --non-interactive - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 diff --git a/digital_image_processing/filters/local_binary_pattern.py b/digital_image_processing/filters/local_binary_pattern.py index 861369ba6a32..ac54ecce755c 100644 --- a/digital_image_processing/filters/local_binary_pattern.py +++ b/digital_image_processing/filters/local_binary_pattern.py @@ -19,7 +19,7 @@ def get_neighbors_pixel( try: return int(image[x_coordinate][y_coordinate] >= center) - except (IndexError, TypeError): + except IndexError, TypeError: return 0 diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 93f6daf1f88c..b1ab33cc9415 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -124,7 +124,7 @@ def _construct_points( else: try: points.append(Point(p[0], p[1])) - except (IndexError, TypeError): + except IndexError, TypeError: print( f"Ignoring deformed point {p}. All points" " must have at least 2 coordinates." diff --git a/dynamic_programming/catalan_numbers.py b/dynamic_programming/catalan_numbers.py index 7b74f2763d43..a62abe36d670 100644 --- a/dynamic_programming/catalan_numbers.py +++ b/dynamic_programming/catalan_numbers.py @@ -71,7 +71,7 @@ def catalan_numbers(upper_limit: int) -> "list[int]": print(f"The Catalan numbers from 0 through {N} are:") print(catalan_numbers(N)) print("Try another upper limit for the sequence: ", end="") - except (NameError, ValueError): + except NameError, ValueError: print("\n********* Invalid input, goodbye! ************\n") import doctest diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index 1fc123fc2b14..ce0abc664cf9 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -73,7 +73,7 @@ def main(): f"{greatest_common_divisor(num_1, num_2)}" ) print(f"By iterative gcd({num_1}, {num_2}) = {gcd_by_iterative(num_1, num_2)}") - except (IndexError, UnboundLocalError, ValueError): + except IndexError, UnboundLocalError, ValueError: print("Wrong input") diff --git a/project_euler/problem_002/sol4.py b/project_euler/problem_002/sol4.py index a13d34fd760e..3341aa1d4569 100644 --- a/project_euler/problem_002/sol4.py +++ b/project_euler/problem_002/sol4.py @@ -56,7 +56,7 @@ def solution(n: int = 4000000) -> int: try: n = int(n) - except (TypeError, ValueError): + except TypeError, ValueError: raise TypeError("Parameter n must be int or castable to int.") if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index d1c0e61cf1a6..dbf9a84f68bb 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -80,7 +80,7 @@ def solution(n: int = 600851475143) -> int: try: n = int(n) - except (TypeError, ValueError): + except TypeError, ValueError: raise TypeError("Parameter n must be int or castable to int.") if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") diff --git a/project_euler/problem_003/sol2.py b/project_euler/problem_003/sol2.py index 0af0daceed06..4c4f88220514 100644 --- a/project_euler/problem_003/sol2.py +++ b/project_euler/problem_003/sol2.py @@ -44,7 +44,7 @@ def solution(n: int = 600851475143) -> int: try: n = int(n) - except (TypeError, ValueError): + except TypeError, ValueError: raise TypeError("Parameter n must be int or castable to int.") if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") diff --git a/project_euler/problem_003/sol3.py b/project_euler/problem_003/sol3.py index e13a0eb74ec1..1a454b618f75 100644 --- a/project_euler/problem_003/sol3.py +++ b/project_euler/problem_003/sol3.py @@ -44,7 +44,7 @@ def solution(n: int = 600851475143) -> int: try: n = int(n) - except (TypeError, ValueError): + except TypeError, ValueError: raise TypeError("Parameter n must be int or castable to int.") if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") diff --git a/project_euler/problem_005/sol1.py b/project_euler/problem_005/sol1.py index 01cbd0e15ff7..f889c420c61d 100644 --- a/project_euler/problem_005/sol1.py +++ b/project_euler/problem_005/sol1.py @@ -47,7 +47,7 @@ def solution(n: int = 20) -> int: try: n = int(n) - except (TypeError, ValueError): + except TypeError, ValueError: raise TypeError("Parameter n must be int or castable to int.") if n <= 0: raise ValueError("Parameter n must be greater than or equal to one.") diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index fd99453c1100..d63b2f2d86ec 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -87,7 +87,7 @@ def solution(nth: int = 10001) -> int: try: nth = int(nth) - except (TypeError, ValueError): + except TypeError, ValueError: raise TypeError("Parameter nth must be int or castable to int.") from None if nth <= 0: raise ValueError("Parameter nth must be greater than or equal to one.") diff --git a/pyproject.toml b/pyproject.toml index f1559d6bc1b1..6ec899d7840f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,6 @@ test = [ "pytest>=8.4.1", "pytest-cov>=6", ] - docs = [ "myst-parser>=4", "sphinx-autoapi>=3.4", @@ -50,7 +49,6 @@ euler-validate = [ [tool.ruff] target-version = "py314" - output-format = "full" lint.select = [ # https://beta.ruff.rs/docs/rules @@ -128,7 +126,6 @@ lint.ignore = [ "SLF001", # Private member accessed: `_Iterator` -- FIX ME "UP037", # FIX ME ] - lint.per-file-ignores."data_structures/hashing/tests/test_hash_map.py" = [ "BLE001", ] @@ -150,37 +147,43 @@ lint.per-file-ignores."project_euler/problem_099/sol1.py" = [ lint.per-file-ignores."sorts/external_sort.py" = [ "SIM115", ] -lint.mccabe.max-complexity = 17 # default: 10 +lint.mccabe.max-complexity = 17 # default: 10 lint.pylint.allow-magic-value-types = [ "float", "int", "str", ] -lint.pylint.max-args = 10 # default: 5 -lint.pylint.max-branches = 20 # default: 12 -lint.pylint.max-returns = 8 # default: 6 -lint.pylint.max-statements = 88 # default: 50 +lint.pylint.max-args = 10 # default: 5 +lint.pylint.max-branches = 20 # default: 12 +lint.pylint.max-returns = 8 # default: 6 +lint.pylint.max-statements = 88 # default: 50 [tool.codespell] ignore-words-list = "3rt,abd,aer,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar" -skip = "./.*,*.json,*.lock,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" +skip = """\ + ./.*,*.json,*.lock,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictio\ + nary.txt,strings/words.txt\ + """ -[tool.pytest.ini_options] -markers = [ +[tool.pytest] +ini_options.markers = [ "mat_ops: mark a test as utilizing matrix operations.", ] -addopts = [ +ini_options.addopts = [ "--durations=10", "--doctest-modules", "--showlocals", ] -[tool.coverage.report] -omit = [ +[tool.coverage] +report.omit = [ ".env/*", "project_euler/*", ] -sort = "Cover" +report.sort = "Cover" + +[tool.mypy] +python_version = "3.14" [tool.sphinx-pyproject] copyright = "2014, TheAlgorithms" @@ -261,7 +264,6 @@ myst_fence_as_directive = [ "include", ] templates_path = [ "_templates" ] -[tool.sphinx-pyproject.source_suffix] -".rst" = "restructuredtext" +source_suffix.".rst" = "restructuredtext" # ".txt" = "markdown" -".md" = "markdown" +source_suffix.".md" = "markdown" diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py index e34a89c19cc8..680d7444bd1c 100644 --- a/web_programming/fetch_well_rx_price.py +++ b/web_programming/fetch_well_rx_price.py @@ -67,7 +67,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None: return pharmacy_price_list - except (httpx.HTTPError, ValueError): + except httpx.HTTPError, ValueError: return None diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py index 68271c1c4643..0b91db01ca09 100644 --- a/web_programming/instagram_crawler.py +++ b/web_programming/instagram_crawler.py @@ -53,7 +53,7 @@ def get_json(self) -> dict: scripts = BeautifulSoup(html, "html.parser").find_all("script") try: return extract_user_profile(scripts[4]) - except (json.decoder.JSONDecodeError, KeyError): + except json.decoder.JSONDecodeError, KeyError: return extract_user_profile(scripts[3]) def __repr__(self) -> str: From 754dac42d9927fd5a727a181fec6c4007c9ebaf5 Mon Sep 17 00:00:00 2001 From: Max <183308611+max-klingner@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:55:59 -0700 Subject: [PATCH 34/38] fix: remove dead code in hamming_code.py to resolve SIM113 (#14509) Co-authored-by: Max <183308611+max938-coder@users.noreply.github.com> --- hashes/hamming_code.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/hashes/hamming_code.py b/hashes/hamming_code.py index b3095852ac51..fead26cf7536 100644 --- a/hashes/hamming_code.py +++ b/hashes/hamming_code.py @@ -118,7 +118,6 @@ def emitter_converter(size_par, data): data_ord.append(None) # Calculates parity - qtd_bp = 0 # parity bit counter for bp in range(1, size_par + 1): # Bit counter one for a given parity cont_bo = 0 @@ -133,8 +132,6 @@ def emitter_converter(size_par, data): cont_bo += 1 parity.append(cont_bo % 2) - qtd_bp += 1 - # Mount the message cont_bp = 0 # parity bit counter for x in range(size_par + len(data)): @@ -208,7 +205,6 @@ def receptor_converter(size_par, data): data_ord.append(None) # Calculates parity - qtd_bp = 0 # parity bit counter for bp in range(1, size_par + 1): # Bit counter one for a certain parity cont_bo = 0 @@ -222,8 +218,6 @@ def receptor_converter(size_par, data): cont_bo += 1 parity.append(str(cont_bo % 2)) - qtd_bp += 1 - # Mount the message cont_bp = 0 # Parity bit counter for x in range(size_par + len(data_output)): From 02680c905522d8c8abae08030dfd754aeb4c2ae4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:03:33 +0300 Subject: [PATCH 35/38] [pre-commit.ci] pre-commit autoupdate (#14513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.4 → v0.15.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.4...v0.15.9) - [github.com/codespell-project/codespell: v2.4.1 → v2.4.2](https://github.com/codespell-project/codespell/compare/v2.4.1...v2.4.2) - [github.com/tox-dev/pyproject-fmt: v2.16.2 → v2.21.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.16.2...v2.21.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- .pre-commit-config.yaml | 6 +++--- pyproject.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e352f7dcb48..39daf3dd7f88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,20 +19,20 @@ repos: - id: auto-walrus - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.4 + rev: v0.15.9 hooks: - id: ruff-check - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.16.2 + rev: v2.21.0 hooks: - id: pyproject-fmt diff --git a/pyproject.toml b/pyproject.toml index 6ec899d7840f..34e099a46435 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,6 +165,9 @@ skip = """\ nary.txt,strings/words.txt\ """ +[tool.mypy] +python_version = "3.14" + [tool.pytest] ini_options.markers = [ "mat_ops: mark a test as utilizing matrix operations.", @@ -182,9 +185,6 @@ report.omit = [ ] report.sort = "Cover" -[tool.mypy] -python_version = "3.14" - [tool.sphinx-pyproject] copyright = "2014, TheAlgorithms" autoapi_dirs = [ From f944b9125d148f5a0e487e445f6196e2e280fca3 Mon Sep 17 00:00:00 2001 From: Aarav Arya <159852168+Aarav-Arya@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:07:41 -0400 Subject: [PATCH 36/38] Add type hints to unknown_sort (#14489) * Added type hints for bogo_sort * Add type hints to unknown_sort * Update bogo_sort.py * Update unknown_sort.py --------- Co-authored-by: Maxim Smolskiy --- sorts/unknown_sort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sorts/unknown_sort.py b/sorts/unknown_sort.py index 9fa9d22fb5e0..3545da68ea80 100644 --- a/sorts/unknown_sort.py +++ b/sorts/unknown_sort.py @@ -6,7 +6,7 @@ """ -def merge_sort(collection): +def merge_sort(collection: list) -> list: """Pure implementation of the fastest merge sort algorithm in Python :param collection: some mutable ordered collection with heterogeneous From fc2f947e0fabb84862737aa7fed05e6322389f0b Mon Sep 17 00:00:00 2001 From: Mahema Date: Sat, 11 Apr 2026 03:37:38 +0530 Subject: [PATCH 37/38] Fix empty input edge case and correct output formatting (#14444) * Fix empty input edge case and correct output formatting * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pigeonhole_sort.py * Update pigeonhole_sort.py * Update pigeonhole_sort.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy --- sorts/pigeonhole_sort.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sorts/pigeonhole_sort.py b/sorts/pigeonhole_sort.py index bfa9bb11b8a6..7fbc6188cfb4 100644 --- a/sorts/pigeonhole_sort.py +++ b/sorts/pigeonhole_sort.py @@ -10,7 +10,11 @@ def pigeonhole_sort(a): >>> pigeonhole_sort(a) # a destructive sort >>> a == b True + + >>> pigeonhole_sort([]) """ + if not a: + return # size of range of values in the list (ie, number of pigeonholes we need) min_val = min(a) # min() finds the minimum value @@ -38,7 +42,7 @@ def pigeonhole_sort(a): def main(): a = [8, 3, 2, 7, 4, 6, 8] pigeonhole_sort(a) - print("Sorted order is:", " ".join(a)) + print("Sorted order is:", *a) if __name__ == "__main__": From 791deb40f93c65a020f2c75e41b55c97ad566cc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:00:57 +0200 Subject: [PATCH 38/38] Bump actions/upload-pages-artifact from 4 to 5 (#14551) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sphinx.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 5f5a90ce1a2d..3f00094e0264 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -41,7 +41,7 @@ jobs: - run: uv sync --group=docs - uses: actions/configure-pages@v6 - run: uv run sphinx-build -c docs . docs/_build/html - - uses: actions/upload-pages-artifact@v4 + - uses: actions/upload-pages-artifact@v5 with: path: docs/_build/html