From b814cf3781a97c273a779823b8b8ab388417b7b4 Mon Sep 17 00:00:00 2001 From: Kiarash Hajian <133909368+kiarash8112@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:53:34 -0400 Subject: [PATCH] add exponential search algorithm (#10732) * add exponential_search algorithm * replace binary_search with binary_search_recursion * convert left type to int to be useable in binary_search_recursion * add docs and tests for exponential_search algorithm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * move exponential_search to binary_search.py to pass github auto build tests delete exponential_search.py file * Update searches/binary_search.py Co-authored-by: Christian Clauss * remove additional space searches/binary_search.py Co-authored-by: Christian Clauss * return single data type in exponential_search searches/binary_search.py Co-authored-by: Christian Clauss * add doctest mod searches/binary_search.py Co-authored-by: Christian Clauss * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * use // instread of int() convert searches/binary_search.py Co-authored-by: Christian Clauss * change test according to new code searches/binary_search.py Co-authored-by: Christian Clauss * fix binary_search_recursion multiple type return error * add a timeit benchmark for exponential_search * sort input of binary search to be equal in performance test with exponential_search * raise value error instead of sorting input in binary and exonential search to fix bugs * Update binary_search.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss Co-authored-by: user --- searches/binary_search.py | 149 +++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 05dadd4fe..586be39c9 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """ -This is pure Python implementation of binary search algorithms +Pure Python implementations of binary search algorithms -For doctests run following command: +For doctests run the following command: python3 -m doctest -v binary_search.py For manual testing run: @@ -34,16 +34,12 @@ def bisect_left( Examples: >>> bisect_left([0, 5, 7, 10, 15], 0) 0 - >>> bisect_left([0, 5, 7, 10, 15], 6) 2 - >>> bisect_left([0, 5, 7, 10, 15], 20) 5 - >>> bisect_left([0, 5, 7, 10, 15], 15, 1, 3) 3 - >>> bisect_left([0, 5, 7, 10, 15], 6, 2) 2 """ @@ -79,16 +75,12 @@ def bisect_right( Examples: >>> bisect_right([0, 5, 7, 10, 15], 0) 1 - >>> bisect_right([0, 5, 7, 10, 15], 15) 5 - >>> bisect_right([0, 5, 7, 10, 15], 6) 2 - >>> bisect_right([0, 5, 7, 10, 15], 15, 1, 3) 3 - >>> bisect_right([0, 5, 7, 10, 15], 6, 2) 2 """ @@ -124,7 +116,6 @@ def insort_left( >>> insort_left(sorted_collection, 6) >>> sorted_collection [0, 5, 6, 7, 10, 15] - >>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)] >>> item = (5, 5) >>> insort_left(sorted_collection, item) @@ -134,12 +125,10 @@ def insort_left( True >>> 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) >>> sorted_collection @@ -167,7 +156,6 @@ def insort_right( >>> insort_right(sorted_collection, 6) >>> sorted_collection [0, 5, 6, 7, 10, 15] - >>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)] >>> item = (5, 5) >>> insort_right(sorted_collection, item) @@ -177,12 +165,10 @@ def insort_right( False >>> 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) >>> sorted_collection @@ -191,29 +177,28 @@ def insort_right( sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) -def binary_search(sorted_collection: list[int], item: int) -> int | None: - """Pure implementation of binary search algorithm in Python +def binary_search(sorted_collection: list[int], item: int) -> int: + """Pure implementation of a binary search algorithm in Python - Be careful collection must be ascending sorted, otherwise result will be + Be careful collection must be ascending sorted otherwise, the result will be unpredictable :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search - :return: index of found item or None if item is not found + :return: index of the found item or -1 if the item is not found Examples: >>> binary_search([0, 5, 7, 10, 15], 0) 0 - >>> binary_search([0, 5, 7, 10, 15], 15) 4 - >>> binary_search([0, 5, 7, 10, 15], 5) 1 - >>> 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 @@ -226,66 +211,66 @@ def binary_search(sorted_collection: list[int], item: int) -> int | None: right = midpoint - 1 else: left = midpoint + 1 - return None + return -1 -def binary_search_std_lib(sorted_collection: list[int], item: int) -> int | None: - """Pure implementation of binary search algorithm in Python using stdlib +def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: + """Pure implementation of a binary search algorithm in Python using stdlib - Be careful collection must be ascending sorted, otherwise result will be + Be careful collection must be ascending sorted otherwise, the result will be unpredictable :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search - :return: index of found item or None if item is not found + :return: index of the found item or -1 if the item is not found Examples: >>> binary_search_std_lib([0, 5, 7, 10, 15], 0) 0 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 15) 4 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 5) 1 - >>> 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 - return None + return -1 def binary_search_by_recursion( - sorted_collection: list[int], item: int, left: int, right: int -) -> int | None: - """Pure implementation of binary search algorithm in Python 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 - Be careful collection must be ascending sorted, otherwise result will be + Be careful collection must be ascending sorted otherwise, the 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 found item or None if item is not found + :return: index of the found item or -1 if the item is not found Examples: >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4) 0 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4) 4 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4) 1 - >>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4) - + -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 None + return -1 midpoint = left + (right - left) // 2 @@ -297,12 +282,78 @@ def binary_search_by_recursion( return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) +def exponential_search(sorted_collection: list[int], item: int) -> int: + """Pure implementation of an 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 + + the order of this algorithm is O(lg I) where I is index position of item if exist + + Examples: + >>> exponential_search([0, 5, 7, 10, 15], 0) + 0 + >>> exponential_search([0, 5, 7, 10, 15], 15) + 4 + >>> exponential_search([0, 5, 7, 10, 15], 5) + 1 + >>> 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") + 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 + + +searches = ( # Fastest to slowest... + binary_search_std_lib, + binary_search, + exponential_search, + binary_search_by_recursion, +) + + if __name__ == "__main__": - user_input = input("Enter numbers separated by comma:\n").strip() + 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:\n")) - result = binary_search(collection, target) - if result is None: + 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} in {collection}.") + print(f"{target} was found at position {result} of {collection}.")