Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
92204dc
fix: update binary search for first occurrence and fix syntax errors
tusharynayaka Mar 3, 2026
ca1c615
fix: binary search first occurrence and resolve global ruff linting e…
tusharynayaka Mar 3, 2026
9fe6d31
fix: binary search first occurrence and resolve global ruff linting e…
tusharynayaka Mar 3, 2026
a1b12ac
fix: binary search first occurrence and resolve global ruff linting e…
tusharynayaka Mar 3, 2026
4dc8470
fix: binary search first occurrence and resolve global ruff linting e…
tusharynayaka Mar 3, 2026
c23157f
fix: binary search first occurrence and resolve global ruff linting e…
tusharynayaka Mar 3, 2026
e94ff2c
fix: total cleanup of jump_search for ruff and mypy
tusharynayaka Mar 3, 2026
3c96273
fix: address ruff I001, UP047, and W292 in jump_search
tusharynayaka Mar 3, 2026
d308b35
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 3, 2026
2662eb5
Merge branch 'master' into fix-binary-search-final
tusharynayaka Mar 18, 2026
a48a595
Merge branch 'master' into fix-binary-search-final
tusharynayaka Mar 31, 2026
d43ff7c
Merge branch 'master' into fix-binary-search-final
tusharynayaka Apr 8, 2026
b13b6e0
fix: remove hidden whitespace and fix newline in jump_search
tusharynayaka Mar 3, 2026
4235f83
Fix: implement first occurrence logic and restore doctests
tusharynayaka Apr 9, 2026
5ede257
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2026
f858e21
style: fix import sorting to satisfy ruff
tusharynayaka Apr 9, 2026
40090f0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2026
f059c6d
final: restore original doctests and fix logic inconsistencies
tusharynayaka Apr 9, 2026
5a9e860
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2026
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
208 changes: 24 additions & 184 deletions searches/binary_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -234,29 +174,14 @@ 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
return -1


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)
Expand All @@ -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 []
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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}.")
Loading
Loading