Skip to content

Commit 4235f83

Browse files
committed
Fix: implement first occurrence logic and restore doctests
1 parent b13b6e0 commit 4235f83

File tree

1 file changed

+41
-63
lines changed

1 file changed

+41
-63
lines changed

searches/binary_search.py

Lines changed: 41 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ def bisect_left(
2121
Locates the first element in a sorted array that is larger or equal to a given
2222
value.
2323
24-
It has the same interface as
25-
https://docs.python.org/3/library/bisect.html#bisect.bisect_left .
26-
2724
:param sorted_collection: some ascending sorted collection with comparable items
2825
:param item: item to bisect
2926
:param lo: lowest index to consider (as in sorted_collection[lo:hi])
@@ -62,27 +59,13 @@ def bisect_right(
6259
"""
6360
Locates the first element in a sorted array that is larger than a given value.
6461
65-
It has the same interface as
66-
https://docs.python.org/3/library/bisect.html#bisect.bisect_right .
67-
68-
:param sorted_collection: some ascending sorted collection with comparable items
69-
:param item: item to bisect
70-
:param lo: lowest index to consider (as in sorted_collection[lo:hi])
71-
:param hi: past the highest index to consider (as in sorted_collection[lo:hi])
72-
:return: index i such that all values in sorted_collection[lo:i] are <= item and
73-
all values in sorted_collection[i:hi] are > item.
74-
7562
Examples:
7663
>>> bisect_right([0, 5, 7, 10, 15], 0)
7764
1
7865
>>> bisect_right([0, 5, 7, 10, 15], 15)
7966
5
8067
>>> bisect_right([0, 5, 7, 10, 15], 6)
8168
2
82-
>>> bisect_right([0, 5, 7, 10, 15], 15, 1, 3)
83-
3
84-
>>> bisect_right([0, 5, 7, 10, 15], 6, 2)
85-
2
8669
"""
8770
if hi < 0:
8871
hi = len(sorted_collection)
@@ -103,9 +86,6 @@ def insort_left(
10386
"""
10487
Inserts a given value into a sorted array before other values with the same value.
10588
106-
It has the same interface as
107-
https://docs.python.org/3/library/bisect.html#bisect.insort_left .
108-
10989
Examples:
11090
>>> sorted_collection = [0, 5, 7, 10, 15]
11191
>>> insort_left(sorted_collection, 6)
@@ -121,9 +101,6 @@ def insort_right(
121101
"""
122102
Inserts a given value into a sorted array after other values with the same value.
123103
124-
It has the same interface as
125-
https://docs.python.org/3/library/bisect.html#bisect.insort_right .
126-
127104
Examples:
128105
>>> sorted_collection = [0, 5, 7, 10, 15]
129106
>>> insort_right(sorted_collection, 6)
@@ -135,7 +112,7 @@ def insort_right(
135112

136113
def binary_search(sorted_collection: list[int], item: int) -> int:
137114
"""Pure implementation of a binary search algorithm in Python.
138-
Finds the first occurrence of the item.
115+
Updated to find the first occurrence of the item.
139116
140117
:param sorted_collection: some ascending sorted collection with comparable items
141118
:param item: item value to search
@@ -153,6 +130,7 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
153130
"""
154131
if any(a > b for a, b in pairwise(sorted_collection)):
155132
raise ValueError("sorted_collection must be sorted in ascending order")
133+
156134
left = 0
157135
right = len(sorted_collection) - 1
158136
result = -1
@@ -170,42 +148,31 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
170148

171149

172150
def binary_search_std_lib(sorted_collection: list[int], item: int) -> int:
173-
"""Implementation of a binary search algorithm using stdlib"""
174-
if list(sorted_collection) != sorted(sorted_collection):
175-
raise ValueError("sorted_collection must be sorted in ascending order")
151+
"""Binary search algorithm in Python using stdlib.
152+
Finds the first occurrence.
153+
154+
>>> binary_search_std_lib([0, 5, 7, 10, 15], 0)
155+
0
156+
>>> binary_search_std_lib([1, 2, 2, 2, 3], 2)
157+
1
158+
"""
176159
index = bisect.bisect_left(sorted_collection, item)
177160
if index != len(sorted_collection) and sorted_collection[index] == item:
178161
return index
179162
return -1
180163

181164

182165
def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> list[int]:
183-
"""Returns a list of all indexes where the target occurs."""
184-
if list(sorted_collection) != sorted(sorted_collection):
185-
raise ValueError("sorted_collection must be sorted in ascending order")
166+
"""Returns a list of all indexes where the target occurs.
186167
187-
def lower_bound(sorted_collection: list[int], item: int) -> int:
188-
left, right = 0, len(sorted_collection)
189-
while left < right:
190-
midpoint = left + (right - left) // 2
191-
if sorted_collection[midpoint] < item:
192-
left = midpoint + 1
193-
else:
194-
right = midpoint
195-
return left
196-
197-
def upper_bound(sorted_collection: list[int], item: int) -> int:
198-
left, right = 0, len(sorted_collection)
199-
while left < right:
200-
midpoint = left + (right - left) // 2
201-
if sorted_collection[midpoint] <= item:
202-
left = midpoint + 1
203-
else:
204-
right = midpoint
205-
return left
206-
207-
left = lower_bound(sorted_collection, item)
208-
right = upper_bound(sorted_collection, item)
168+
Examples:
169+
>>> binary_search_with_duplicates([1, 2, 2, 2, 3], 2)
170+
[1, 2, 3]
171+
>>> binary_search_with_duplicates([1, 2, 2, 2, 3], 4)
172+
[]
173+
"""
174+
left = bisect_left(sorted_collection, item)
175+
right = bisect_right(sorted_collection, item)
209176

210177
if left == len(sorted_collection) or sorted_collection[left] != item:
211178
return []
@@ -215,30 +182,41 @@ def upper_bound(sorted_collection: list[int], item: int) -> int:
215182
def binary_search_by_recursion(
216183
sorted_collection: list[int], item: int, left: int = 0, right: int = -1
217184
) -> int:
218-
"""Recursive binary search finding the first occurrence."""
185+
"""
186+
Recursive binary search finding the first occurrence.
187+
"""
219188
if right < 0:
220189
right = len(sorted_collection) - 1
221-
if list(sorted_collection) != sorted(sorted_collection):
222-
raise ValueError("sorted_collection must be sorted in ascending order")
190+
191+
# Base case: range is empty
223192
if right < left:
224193
return -1
225194

226195
midpoint = left + (right - left) // 2
227196

228197
if sorted_collection[midpoint] == item:
229-
# Check if there is an occurrence to the left
230-
res = binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
231-
return res if res != -1 else midpoint
198+
# We found a match! Now see if there's an earlier one to the left.
199+
# CRITICAL: Only recurse if there is actually space to the left
200+
if midpoint > left:
201+
res = binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
202+
return res if res != -1 else midpoint
203+
return midpoint
204+
232205
elif sorted_collection[midpoint] > item:
233206
return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
234207
else:
235208
return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right)
236209

237210

238211
def exponential_search(sorted_collection: list[int], item: int) -> int:
239-
"""Implementation of an exponential search algorithm."""
240-
if list(sorted_collection) != sorted(sorted_collection):
241-
raise ValueError("sorted_collection must be sorted in ascending order")
212+
"""Implementation of an exponential search algorithm finding the first occurrence.
213+
214+
Examples:
215+
>>> exponential_search([0, 5, 7, 10, 15], 0)
216+
0
217+
>>> exponential_search([1, 2, 2, 2, 3], 2)
218+
1
219+
"""
242220
if not sorted_collection:
243221
return -1
244222
bound = 1
@@ -273,6 +251,6 @@ def exponential_search(sorted_collection: list[int], item: int) -> int:
273251
print(
274252
f"{name:>26}:",
275253
timeit.timeit(
276-
f"{name}(collection, 500)", setup=setup, number=5_000, globals=globals()
254+
f"{name}(list(collection), 500)", setup=setup, number=5_000, globals=globals()
277255
),
278-
)
256+
)

0 commit comments

Comments
 (0)