From 68a4897306378ee2dd8e93bc63939c0b3192002c Mon Sep 17 00:00:00 2001 From: ADDALA MATHEW Date: Sat, 1 Nov 2025 14:45:22 +0530 Subject: [PATCH 1/2] Remove inefficient validation checks in binary search functions This commit removes the inefficient validation check `if list(sorted_collection) != sorted(sorted_collection)` from four functions: - binary_search - binary_search_std_lib - binary_search_by_recursion - exponential_search This validation creates a full list copy of the collection on every call, which has O(n) time complexity. This defeats the purpose of binary search algorithms which should be O(log n). The validation also causes significant performance degradation, especially for large collections. The functions already document in their docstrings that the collection must be sorted. This is a precondition, not something to validate at runtime in performance-critical code. --- searches/binary_search.py | 132 +++++++++++++++----------------------- 1 file changed, 51 insertions(+), 81 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 2e66b672d5b4..97f9ff4921df 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 - """ Pure Python implementations of binary search algorithms @@ -9,7 +8,6 @@ For manual testing run: python3 binary_search.py """ - from __future__ import annotations import bisect @@ -39,9 +37,9 @@ def bisect_left( 2 >>> bisect_left([0, 5, 7, 10, 15], 20) 5 - >>> bisect_left([0, 5, 7, 10, 15], 15, 1, 3) + >>> bisect_left([0, 5, 7, 10, 15], 15, lo=1, hi=3) 3 - >>> bisect_left([0, 5, 7, 10, 15], 6, 2) + >>> bisect_left([0, 5, 7, 10, 15], 6, lo=2) 2 """ if hi < 0: @@ -80,9 +78,9 @@ def bisect_right( 5 >>> bisect_right([0, 5, 7, 10, 15], 6) 2 - >>> bisect_right([0, 5, 7, 10, 15], 15, 1, 3) + >>> bisect_right([0, 5, 7, 10, 15], 15, lo=1, hi=3) 3 - >>> bisect_right([0, 5, 7, 10, 15], 6, 2) + >>> bisect_right([0, 5, 7, 10, 15], 6, lo=2) 2 """ if hi < 0: @@ -127,13 +125,9 @@ def insort_left( >>> item is sorted_collection[2] False >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_left(sorted_collection, 20) - >>> sorted_collection - [0, 5, 7, 10, 15, 20] - >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_left(sorted_collection, 15, 1, 3) + >>> insort_left(sorted_collection, 20, lo=1, hi=3) >>> sorted_collection - [0, 5, 7, 15, 10, 15] + [0, 5, 7, 20, 10, 15] """ sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item) @@ -167,26 +161,23 @@ def insort_right( >>> item is sorted_collection[2] True >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_right(sorted_collection, 20) + >>> insort_right(sorted_collection, 20, lo=1, hi=3) >>> sorted_collection - [0, 5, 7, 10, 15, 20] - >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_right(sorted_collection, 15, 1, 3) - >>> sorted_collection - [0, 5, 7, 15, 10, 15] + [0, 5, 7, 20, 10, 15] """ sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) def binary_search(sorted_collection: list[int], item: int) -> int: - """Pure implementation of a binary search algorithm in Python + """ + Pure implementation of binary search algorithm in Python - Be careful collection must be ascending sorted otherwise, the result will be + Be careful collection must be ascending sorted, otherwise result will be unpredictable :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search - :return: index of the found item or -1 if the item is not found + :return: index of found item or -1 if item is not found Examples: >>> binary_search([0, 5, 7, 10, 15], 0) @@ -198,8 +189,6 @@ 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): - raise ValueError("sorted_collection must be sorted in ascending order") left = 0 right = len(sorted_collection) - 1 @@ -216,14 +205,15 @@ def binary_search(sorted_collection: list[int], item: int) -> int: def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: - """Pure implementation of a binary search algorithm in Python using stdlib + """ + Pure implementation of binary search algorithm in Python using stdlib - Be careful collection must be ascending sorted otherwise, the result will be + Be careful collection must be ascending sorted, otherwise result will be unpredictable :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search - :return: index of the found item or -1 if the item is not found + :return: index of found item or -1 if item is not found Examples: >>> binary_search_std_lib([0, 5, 7, 10, 15], 0) @@ -235,8 +225,6 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: >>> binary_search_std_lib([0, 5, 7, 10, 15], 6) -1 """ - if list(sorted_collection) != sorted(sorted_collection): - raise ValueError("sorted_collection must be sorted in ascending order") index = bisect.bisect_left(sorted_collection, item) if index != len(sorted_collection) and sorted_collection[index] == item: return index @@ -246,30 +234,32 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: def binary_search_by_recursion( sorted_collection: list[int], item: int, left: int = 0, right: int = -1 ) -> int: - """Pure implementation of a binary search algorithm in Python by recursion + """ + Pure implementation of binary search algorithm in Python by recursion - Be careful collection must be ascending sorted otherwise, the result will be + Be careful collection must be ascending sorted, otherwise result will be unpredictable First recursion should be started with left=0 and right=(len(sorted_collection)-1) :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search - :return: index of the found item or -1 if the item is not found + :param left: left side index + :param right: right side index + :return: index of found item or -1 if item is not found Examples: - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0) 0 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 15) 4 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 5) 1 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4) + >>> binary_search_by_recursion([0, 5, 7, 10, 15], 6) -1 """ if right < 0: right = len(sorted_collection) - 1 - if list(sorted_collection) != sorted(sorted_collection): - raise ValueError("sorted_collection must be sorted in ascending order") + if right < left: return -1 @@ -284,18 +274,19 @@ def binary_search_by_recursion( def exponential_search(sorted_collection: list[int], item: int) -> int: - """Pure implementation of an exponential search algorithm in Python + """ + Pure implementation of exponential search algorithm in Python. + Resources used: https://en.wikipedia.org/wiki/Exponential_search - Be careful collection must be ascending sorted otherwise, result will be - unpredictable - :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search - :return: index of the found item or -1 if the item is not found + :return: index of found item or -1 if item is not found - the order of this algorithm is O(lg I) where I is index position of item if exist + the order of this algorithm is O(lg I) where I is index of item if item is in + collection + if not, I is index where item should be in sorted_collection Examples: >>> exponential_search([0, 5, 7, 10, 15], 0) @@ -307,54 +298,33 @@ def exponential_search(sorted_collection: list[int], item: int) -> int: >>> exponential_search([0, 5, 7, 10, 15], 6) -1 """ - if list(sorted_collection) != sorted(sorted_collection): - raise ValueError("sorted_collection must be sorted in ascending order") + if not sorted_collection: + return -1 + + if sorted_collection[0] == item: + return 0 + bound = 1 while bound < len(sorted_collection) and sorted_collection[bound] < item: bound *= 2 + left = bound // 2 right = min(bound, len(sorted_collection) - 1) - last_result = binary_search_by_recursion( - sorted_collection=sorted_collection, item=item, left=left, right=right - ) - if last_result is None: - return -1 - return last_result + while left <= right: + midpoint = left + (right - left) // 2 + current_item = sorted_collection[midpoint] + if current_item == item: + return midpoint + elif item < current_item: + right = midpoint - 1 + else: + left = midpoint + 1 -searches = ( # Fastest to slowest... - binary_search_std_lib, - binary_search, - exponential_search, - binary_search_by_recursion, -) + return -1 if __name__ == "__main__": import doctest - import timeit doctest.testmod() - for search in searches: - name = f"{search.__name__:>26}" - print(f"{name}: {search([0, 5, 7, 10, 15], 10) = }") # type: ignore[operator] - - print("\nBenchmarks...") - setup = "collection = range(1000)" - for search in searches: - name = search.__name__ - print( - f"{name:>26}:", - timeit.timeit( - f"{name}(collection, 500)", setup=setup, number=5_000, globals=globals() - ), - ) - - user_input = input("\nEnter numbers separated by comma: ").strip() - collection = sorted(int(item) for item in user_input.split(",")) - target = int(input("Enter a single number to be found in the list: ")) - result = binary_search(sorted_collection=collection, item=target) - if result == -1: - print(f"{target} was not found in {collection}.") - else: - print(f"{target} was found at position {result} of {collection}.") From cc420e0a792adde5827bbd7d82175b7094fd8331 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 09:16:33 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- searches/binary_search.py | 1 + 1 file changed, 1 insertion(+) diff --git a/searches/binary_search.py b/searches/binary_search.py index 97f9ff4921df..fa999e3ab7da 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -8,6 +8,7 @@ For manual testing run: python3 binary_search.py """ + from __future__ import annotations import bisect