Skip to content

add exponential search algorithm #10732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Oct 21, 2023
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
703f3b8
add exponential_search algorithm
kiarash8112 Oct 20, 2023
dbbe135
replace binary_search with binary_search_recursion
kiarash8112 Oct 20, 2023
1d79bd0
convert left type to int to be useable in binary_search_recursion
kiarash8112 Oct 20, 2023
ff30ae0
add docs and tests for exponential_search algorithm
kiarash8112 Oct 20, 2023
f66e566
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 20, 2023
16f58e1
move exponential_search to binary_search.py to pass github auto build…
kiarash8112 Oct 20, 2023
26b5283
Update searches/binary_search.py
kiarash8112 Oct 21, 2023
63bf03a
remove additional space searches/binary_search.py
kiarash8112 Oct 21, 2023
4ca8a6a
return single data type in exponential_search searches/binary_search.py
kiarash8112 Oct 21, 2023
b524404
add doctest mod searches/binary_search.py
kiarash8112 Oct 21, 2023
dc9155a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2023
afbd458
use // instread of int() convert searches/binary_search.py
kiarash8112 Oct 21, 2023
d8dfe74
change test according to new code searches/binary_search.py
kiarash8112 Oct 21, 2023
66a03cb
fix binary_search_recursion multiple type return error
Oct 21, 2023
d76119b
add a timeit benchmark for exponential_search
Oct 21, 2023
dc32f86
sort input of binary search to be equal in performance test with expo…
Oct 21, 2023
0b53cdd
raise value error instead of sorting input in binary and exonential s…
Oct 21, 2023
49744fb
Update binary_search.py
cclauss Oct 21, 2023
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
149 changes: 100 additions & 49 deletions searches/binary_search.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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

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

Expand All @@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tianyizheng02 exponential_search() embeds a call to binary_search_by_recursion() but as the benchmarks demonstrate delivers a faster result. Looking at the first commit makes this clearer.

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