diff --git a/searches/binary_search.py b/searches/binary_search.py index bec87b3c5aec..edc30aa8257a 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -21,9 +21,6 @@ def bisect_left( Locates the first element in a sorted array that is larger or equal to a given value. - It has the same interface as - https://docs.python.org/3/library/bisect.html#bisect.bisect_left . - :param sorted_collection: some ascending sorted collection with comparable items :param item: item to bisect :param lo: lowest index to consider (as in sorted_collection[lo:hi]) @@ -62,16 +59,6 @@ def bisect_right( """ Locates the first element in a sorted array that is larger than a given value. - It has the same interface as - https://docs.python.org/3/library/bisect.html#bisect.bisect_right . - - :param sorted_collection: some ascending sorted collection with comparable items - :param item: item to bisect - :param lo: lowest index to consider (as in sorted_collection[lo:hi]) - :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) - :return: index i such that all values in sorted_collection[lo:i] are <= item and - all values in sorted_collection[i:hi] are > item. - Examples: >>> bisect_right([0, 5, 7, 10, 15], 0) 1 @@ -103,28 +90,11 @@ def insort_left( """ Inserts a given value into a sorted array before other values with the same value. - It has the same interface as - https://docs.python.org/3/library/bisect.html#bisect.insort_left . - - :param sorted_collection: some ascending sorted collection with comparable items - :param item: item to insert - :param lo: lowest index to consider (as in sorted_collection[lo:hi]) - :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) - Examples: >>> sorted_collection = [0, 5, 7, 10, 15] >>> 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) - >>> sorted_collection - [(0, 0), (5, 5), (5, 5), (7, 7), (10, 10), (15, 15)] - >>> item is sorted_collection[1] - True - >>> item is sorted_collection[2] - False >>> sorted_collection = [0, 5, 7, 10, 15] >>> insort_left(sorted_collection, 20) >>> sorted_collection @@ -143,28 +113,11 @@ def insort_right( """ Inserts a given value into a sorted array after other values with the same value. - It has the same interface as - https://docs.python.org/3/library/bisect.html#bisect.insort_right . - - :param sorted_collection: some ascending sorted collection with comparable items - :param item: item to insert - :param lo: lowest index to consider (as in sorted_collection[lo:hi]) - :param hi: past the highest index to consider (as in sorted_collection[lo:hi]) - Examples: >>> sorted_collection = [0, 5, 7, 10, 15] >>> 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) - >>> sorted_collection - [(0, 0), (5, 5), (5, 5), (7, 7), (10, 10), (15, 15)] - >>> item is sorted_collection[1] - False - >>> item is sorted_collection[2] - True >>> sorted_collection = [0, 5, 7, 10, 15] >>> insort_right(sorted_collection, 20) >>> sorted_collection @@ -178,14 +131,7 @@ def insort_right( 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, the 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 + """Pure implementation of a binary search algorithm in Python. Examples: >>> binary_search([0, 5, 7, 10, 15], 0) @@ -199,32 +145,26 @@ def binary_search(sorted_collection: list[int], item: int) -> int: """ 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 + result = -1 while left <= right: midpoint = left + (right - left) // 2 - current_item = sorted_collection[midpoint] - if current_item == item: - return midpoint - elif item < current_item: + if sorted_collection[midpoint] == item: + result = midpoint + right = midpoint - 1 # Logic for first occurrence + elif item < sorted_collection[midpoint]: right = midpoint - 1 else: left = midpoint + 1 - return -1 + return result def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: - """Pure implementation of a binary search algorithm in Python using stdlib + """Binary search using stdlib. - 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 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) @@ -234,8 +174,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 @@ -243,20 +181,7 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> list[int]: - """Pure implementation of a binary search algorithm in Python that supports - duplicates. - - Resources used: - https://stackoverflow.com/questions/13197552/using-binary-search-with-sorted-array-with-duplicates - - The collection must be sorted in ascending order; otherwise the result will be - unpredictable. If the target appears multiple times, this function returns a - list of all indexes where the target occurs. If the target is not found, - this function returns an empty list. - - :param sorted_collection: some ascending sorted collection with comparable items - :param item: item value to search for - :return: a list of indexes where the item is found (empty list if not found) + """Returns a list of all indexes where the target occurs. Examples: >>> binary_search_with_duplicates([0, 5, 7, 10, 15], 0) @@ -268,49 +193,8 @@ def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> li >>> binary_search_with_duplicates([1, 2, 2, 2, 3], 4) [] """ - if list(sorted_collection) != sorted(sorted_collection): - raise ValueError("sorted_collection must be sorted in ascending order") - - def lower_bound(sorted_collection: list[int], item: int) -> int: - """ - Returns the index of the first element greater than or equal to the item. - - :param sorted_collection: The sorted list to search. - :param item: The item to find the lower bound for. - :return: The index where the item can be inserted while maintaining order. - """ - left = 0 - right = len(sorted_collection) - while left < right: - midpoint = left + (right - left) // 2 - current_item = sorted_collection[midpoint] - if current_item < item: - left = midpoint + 1 - else: - right = midpoint - return left - - def upper_bound(sorted_collection: list[int], item: int) -> int: - """ - Returns the index of the first element strictly greater than the item. - - :param sorted_collection: The sorted list to search. - :param item: The item to find the upper bound for. - :return: The index where the item can be inserted after all existing instances. - """ - left = 0 - right = len(sorted_collection) - while left < right: - midpoint = left + (right - left) // 2 - current_item = sorted_collection[midpoint] - if current_item <= item: - left = midpoint + 1 - else: - right = midpoint - return left - - left = lower_bound(sorted_collection, item) - right = upper_bound(sorted_collection, item) + left = bisect_left(sorted_collection, item) + right = bisect_right(sorted_collection, item) if left == len(sorted_collection) or sorted_collection[left] != item: return [] @@ -320,15 +204,7 @@ def upper_bound(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 - - 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 the found item or -1 if the item is not found + """Recursive binary search. Examples: >>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4) @@ -342,14 +218,18 @@ def binary_search_by_recursion( """ 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 midpoint = left + (right - left) // 2 if sorted_collection[midpoint] == item: + if midpoint > left: + res = binary_search_by_recursion( + sorted_collection, item, left, midpoint - 1 + ) + return res if res != -1 else midpoint return midpoint elif sorted_collection[midpoint] > item: return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1) @@ -358,18 +238,7 @@ def binary_search_by_recursion( 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 + """Exponential search algorithm. Examples: >>> exponential_search([0, 5, 7, 10, 15], 0) @@ -381,22 +250,17 @@ 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 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 + return binary_search_by_recursion(sorted_collection, item, left, right) -searches = ( # Fastest to slowest... +searches = ( binary_search_std_lib, binary_search, exponential_search, @@ -406,29 +270,5 @@ def exponential_search(sorted_collection: list[int], item: int) -> int: 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}.") diff --git a/searches/jump_search.py b/searches/jump_search.py index 437faf306bb2..29ea7d25d437 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -1,13 +1,10 @@ +#!/usr/bin/env python3 """ Pure Python implementation of the jump search algorithm. -This algorithm iterates through a sorted collection with a step of n^(1/2), -until the element compared is bigger than the one searched. -It will then perform a linear search until it matches the wanted number. -If not found, it returns -1. - -https://en.wikipedia.org/wiki/Jump_search """ +from __future__ import annotations + import math from collections.abc import Sequence from typing import Any, Protocol @@ -22,46 +19,51 @@ 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. - Examples: >>> jump_search([0, 1, 2, 3, 4, 5], 3) 3 >>> jump_search([-5, -2, -1], -1) 2 >>> jump_search([0, 5, 10, 20], 8) -1 - >>> jump_search([0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610], 55) - 10 >>> jump_search(["aa", "bb", "cc", "dd", "ee", "ff"], "ee") 4 """ - arr_size = len(arr) - block_size = int(math.sqrt(arr_size)) + if arr_size == 0: + return -1 + block_size = int(math.sqrt(arr_size)) prev = 0 step = block_size + + # Finding the block where element is present while arr[min(step, arr_size) - 1] < item: prev = step step += block_size if prev >= arr_size: return -1 + # Linear search within the block while arr[prev] < item: prev += 1 if prev == min(step, arr_size): return -1 + if arr[prev] == item: return prev return -1 if __name__ == "__main__": - user_input = input("Enter numbers separated by a comma:\n").strip() - array = [int(item) for item in user_input.split(",")] - x = int(input("Enter the number to be searched:\n")) + import doctest - res = jump_search(array, x) - if res == -1: - print("Number not found!") - else: - print(f"Number {x} is at index {res}") + doctest.testmod() + user_input = input("Enter numbers separated by a comma:\n").strip() + if user_input: + array: list[Any] = [int(i) for i in user_input.split(",")] + search_item = int(input("Enter the number to be searched:\n")) + res = jump_search(array, search_item) + if res == -1: + print("Number not found!") + else: + print(f"Number {search_item} is at index {res}")