diff --git a/searches/binary_search.py b/searches/binary_search.py index 2e66b672d5b4..fa999e3ab7da 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 @@ -39,9 +38,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 +79,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 +126,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 +162,23 @@ def insort_right( >>> item is sorted_collection[2] True >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_right(sorted_collection, 20) - >>> sorted_collection - [0, 5, 7, 10, 15, 20] - >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_right(sorted_collection, 15, 1, 3) + >>> insort_right(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_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 +190,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 +206,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 +226,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 +235,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 +275,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 +299,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}.")