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/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 8528ccbbae51..af5248c4dbc4 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -252,7 +252,7 @@ def accuracy(actual_y: list, predicted_y: list) -> float: 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/binary_search.py b/searches/binary_search.py index 5125dc6bdb9a..65b408a636eb 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -107,33 +107,11 @@ def insort_left( 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 - [0, 5, 7, 10, 15, 20] - >>> sorted_collection = [0, 5, 7, 10, 15] - >>> insort_left(sorted_collection, 15, 1, 3) - >>> sorted_collection - [0, 5, 7, 15, 10, 15] """ sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item) @@ -147,42 +125,18 @@ def insort_right( 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 - [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] """ 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 - - Be careful collection must be ascending sorted otherwise, the result will be - unpredictable + """Pure implementation of a binary search algorithm in Python. + Finds the first occurrence of the item. :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search @@ -193,7 +147,7 @@ def binary_search(sorted_collection: list[int], item: int) -> int: 0 >>> binary_search([0, 5, 7, 10, 15], 15) 4 - >>> binary_search([0, 5, 7, 10, 15], 5) + >>> binary_search([1, 2, 2, 2, 3], 2) 1 >>> binary_search([0, 5, 7, 10, 15], 6) -1 @@ -202,39 +156,22 @@ def binary_search(sorted_collection: list[int], item: int) -> int: 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 # Continue searching left + 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 - - 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) - 4 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 5) - 1 - >>> binary_search_std_lib([0, 5, 7, 10, 15], 6) - -1 - """ + """Implementation of a binary search algorithm using stdlib""" if list(sorted_collection) != sorted(sorted_collection): raise ValueError("sorted_collection must be sorted in ascending order") index = bisect.bisect_left(sorted_collection, item) @@ -244,67 +181,25 @@ 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) - - Examples: - >>> binary_search_with_duplicates([0, 5, 7, 10, 15], 0) - [0] - >>> binary_search_with_duplicates([0, 5, 7, 10, 15], 15) - [4] - >>> binary_search_with_duplicates([1, 2, 2, 2, 3], 2) - [1, 2, 3] - >>> binary_search_with_duplicates([1, 2, 2, 2, 3], 4) - [] - """ + """Returns a list of all indexes where the target occurs.""" 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) + left, right = 0, len(sorted_collection) while left < right: midpoint = left + (right - left) // 2 - current_item = sorted_collection[midpoint] - if current_item < item: + if sorted_collection[midpoint] < 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) + left, right = 0, len(sorted_collection) while left < right: midpoint = left + (right - left) // 2 - current_item = sorted_collection[midpoint] - if current_item <= item: + if sorted_collection[midpoint] <= item: left = midpoint + 1 else: right = midpoint @@ -321,26 +216,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 - - 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 - """ + """Recursive binary search finding the first occurrence.""" if right < 0: right = len(sorted_collection) - 1 if list(sorted_collection) != sorted(sorted_collection): @@ -351,7 +227,9 @@ def binary_search_by_recursion( midpoint = left + (right - left) // 2 if sorted_collection[midpoint] == item: - return midpoint + # Check if there is an occurrence to the left + res = binary_search_by_recursion(sorted_collection, item, left, midpoint - 1) + return res if res != -1 else midpoint elif sorted_collection[midpoint] > item: return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1) else: @@ -359,45 +237,20 @@ 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 - - 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 - """ + """Implementation of an exponential search algorithm.""" 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, @@ -412,7 +265,7 @@ def exponential_search(sorted_collection: list[int], item: int) -> int: 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(f"{name}: {search([0, 5, 7, 10, 15], 10) = }") print("\nBenchmarks...") setup = "collection = range(1000)" @@ -424,12 +277,3 @@ def exponential_search(sorted_collection: list[int], item: int) -> int: 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 e72d85e8a868..ea6f699f63f8 100644 --- a/searches/jump_search.py +++ b/searches/jump_search.py @@ -1,70 +1,68 @@ +#!/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, 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: list[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}")