Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 51 additions & 80 deletions searches/binary_search.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3

"""
Pure Python implementations of binary search algorithms

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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}.")