Skip to content

Commit 756557e

Browse files
committed
fix: binary search logic and clean formatting
1 parent fb32b49 commit 756557e

File tree

1 file changed

+23
-156
lines changed

1 file changed

+23
-156
lines changed

searches/binary_search.py

Lines changed: 23 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,6 @@ def insort_left(
126126
True
127127
>>> item is sorted_collection[2]
128128
False
129-
>>> sorted_collection = [0, 5, 7, 10, 15]
130-
>>> insort_left(sorted_collection, 20)
131-
>>> sorted_collection
132-
[0, 5, 7, 10, 15, 20]
133-
>>> sorted_collection = [0, 5, 7, 10, 15]
134-
>>> insort_left(sorted_collection, 15, 1, 3)
135-
>>> sorted_collection
136-
[0, 5, 7, 15, 10, 15]
137129
"""
138130
sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item)
139131

@@ -157,23 +149,6 @@ def insort_right(
157149
>>> insort_right(sorted_collection, 6)
158150
>>> sorted_collection
159151
[0, 5, 6, 7, 10, 15]
160-
>>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)]
161-
>>> item = (5, 5)
162-
>>> insort_right(sorted_collection, item)
163-
>>> sorted_collection
164-
[(0, 0), (5, 5), (5, 5), (7, 7), (10, 10), (15, 15)]
165-
>>> item is sorted_collection[1]
166-
False
167-
>>> item is sorted_collection[2]
168-
True
169-
>>> sorted_collection = [0, 5, 7, 10, 15]
170-
>>> insort_right(sorted_collection, 20)
171-
>>> sorted_collection
172-
[0, 5, 7, 10, 15, 20]
173-
>>> sorted_collection = [0, 5, 7, 10, 15]
174-
>>> insort_right(sorted_collection, 15, 1, 3)
175-
>>> sorted_collection
176-
[0, 5, 7, 15, 10, 15]
177152
"""
178153
sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item)
179154

@@ -182,9 +157,6 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
182157
"""Pure implementation of a binary search algorithm in Python.
183158
Finds the first occurrence of the item.
184159
185-
Be careful collection must be ascending sorted otherwise, the result will be
186-
unpredictable
187-
188160
:param sorted_collection: some ascending sorted collection with comparable items
189161
:param item: item value to search
190162
:return: index of the first found item or -1 if the item is not found
@@ -194,10 +166,6 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
194166
0
195167
>>> binary_search([0, 5, 7, 10, 15], 15)
196168
4
197-
>>> binary_search([0, 5, 7, 10, 15], 5)
198-
1
199-
>>> binary_search([0, 5, 7, 10, 15], 6)
200-
-1
201169
>>> binary_search([1, 2, 2, 2, 3], 2)
202170
1
203171
"""
@@ -212,7 +180,7 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
212180
current_item = sorted_collection[midpoint]
213181
if current_item == item:
214182
result = midpoint
215-
right = midpoint - 1 # Search left for first occurrence
183+
right = midpoint - 1
216184
elif item < current_item:
217185
right = midpoint - 1
218186
else:
@@ -221,25 +189,7 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
221189

222190

223191
def binary_search_std_lib(sorted_collection: list[int], item: int) -> int:
224-
"""Pure implementation of a binary search algorithm in Python using stdlib
225-
226-
Be careful collection must be ascending sorted otherwise, the result will be
227-
unpredictable
228-
229-
:param sorted_collection: some ascending sorted collection with comparable items
230-
:param item: item value to search
231-
:return: index of the found item or -1 if the item is not found
232-
233-
Examples:
234-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 0)
235-
0
236-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 15)
237-
4
238-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 5)
239-
1
240-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 6)
241-
-1
242-
"""
192+
"""Pure implementation using stdlib"""
243193
if list(sorted_collection) != sorted(sorted_collection):
244194
raise ValueError("sorted_collection must be sorted in ascending order")
245195
index = bisect.bisect_left(sorted_collection, item)
@@ -249,54 +199,28 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int:
249199

250200

251201
def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> list[int]:
252-
"""Pure implementation of a binary search algorithm in Python that supports
253-
duplicates.
254-
255-
Resources used:
256-
https://stackoverflow.com/questions/13197552/using-binary-search-with-sorted-array-with-duplicates
257-
258-
The collection must be sorted in ascending order; otherwise the result will be
259-
unpredictable. If the target appears multiple times, this function returns a
260-
list of all indexes where the target occurs. If the target is not found,
261-
this function returns an empty list.
262-
263-
:param sorted_collection: some ascending sorted collection with comparable items
264-
:param item: item value to search for
265-
:return: a list of indexes where the item is found (empty list if not found)
266-
267-
Examples:
268-
>>> binary_search_with_duplicates([0, 5, 7, 10, 15], 0)
269-
[0]
270-
>>> binary_search_with_duplicates([0, 5, 7, 10, 15], 15)
271-
[4]
272-
>>> binary_search_with_duplicates([1, 2, 2, 2, 3], 2)
273-
[1, 2, 3]
274-
>>> binary_search_with_duplicates([1, 2, 2, 2, 3], 4)
275-
[]
276-
"""
202+
"""Returns a list of all indexes where the item is found."""
277203
if list(sorted_collection) != sorted(sorted_collection):
278204
raise ValueError("sorted_collection must be sorted in ascending order")
279205

280206
def lower_bound(sorted_collection: list[int], item: int) -> int:
281-
left = 0
282-
right = len(sorted_collection)
207+
left, right = 0, len(sorted_collection)
283208
while left < right:
284-
midpoint = left + (right - left) // 2
285-
if sorted_collection[midpoint] < item:
286-
left = midpoint + 1
209+
mid = left + (right - left) // 2
210+
if sorted_collection[mid] < item:
211+
left = mid + 1
287212
else:
288-
right = midpoint
213+
right = mid
289214
return left
290215

291216
def upper_bound(sorted_collection: list[int], item: int) -> int:
292-
left = 0
293-
right = len(sorted_collection)
217+
left, right = 0, len(sorted_collection)
294218
while left < right:
295-
midpoint = left + (right - left) // 2
296-
if sorted_collection[midpoint] <= item:
297-
left = midpoint + 1
219+
mid = left + (right - left) // 2
220+
if sorted_collection[mid] <= item:
221+
left = mid + 1
298222
else:
299-
right = midpoint
223+
right = mid
300224
return left
301225

302226
left = lower_bound(sorted_collection, item)
@@ -310,29 +234,7 @@ def upper_bound(sorted_collection: list[int], item: int) -> int:
310234
def binary_search_by_recursion(
311235
sorted_collection: list[int], item: int, left: int = 0, right: int = -1
312236
) -> int:
313-
"""Pure implementation of a binary search algorithm in Python by recursion.
314-
Finds the first occurrence of the item.
315-
316-
Be careful collection must be ascending sorted otherwise, the result will be
317-
unpredictable
318-
First recursion should be started with left=0 and right=(len(sorted_collection)-1)
319-
320-
:param sorted_collection: some ascending sorted collection with comparable items
321-
:param item: item value to search
322-
:return: index of the first found item or -1 if the item is not found
323-
324-
Examples:
325-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4)
326-
0
327-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4)
328-
4
329-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4)
330-
1
331-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4)
332-
-1
333-
>>> binary_search_by_recursion([1, 2, 2, 2, 3], 2, 0, 4)
334-
1
335-
"""
237+
"""Recursive implementation finding the first occurrence."""
336238
if right < 0:
337239
right = len(sorted_collection) - 1
338240
if list(sorted_collection) != sorted(sorted_collection):
@@ -343,7 +245,6 @@ def binary_search_by_recursion(
343245
midpoint = left + (right - left) // 2
344246

345247
if sorted_collection[midpoint] == item:
346-
# Check if there's an earlier occurrence to the left
347248
res = binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
348249
return res if res != -1 else midpoint
349250
elif sorted_collection[midpoint] > item:
@@ -353,45 +254,20 @@ def binary_search_by_recursion(
353254

354255

355256
def exponential_search(sorted_collection: list[int], item: int) -> int:
356-
"""Pure implementation of an exponential search algorithm in Python
357-
Resources used:
358-
https://en.wikipedia.org/wiki/Exponential_search
359-
360-
Be careful collection must be ascending sorted otherwise, result will be
361-
unpredictable
362-
363-
:param sorted_collection: some ascending sorted collection with comparable items
364-
:param item: item value to search
365-
:return: index of the found item or -1 if the item is not found
366-
367-
the order of this algorithm is O(lg I) where I is index position of item if exist
368-
369-
Examples:
370-
>>> exponential_search([0, 5, 7, 10, 15], 0)
371-
0
372-
>>> exponential_search([0, 5, 7, 10, 15], 15)
373-
4
374-
>>> exponential_search([0, 5, 7, 10, 15], 5)
375-
1
376-
>>> exponential_search([0, 5, 7, 10, 15], 6)
377-
-1
378-
"""
379-
if list(sorted_collection) != sorted(sorted_collection):
380-
raise ValueError("sorted_collection must be sorted in ascending order")
257+
"""Exponential search implementation."""
258+
if not sorted_collection:
259+
return -1
260+
if sorted_collection[0] == item:
261+
return 0
381262
bound = 1
382263
while bound < len(sorted_collection) and sorted_collection[bound] < item:
383264
bound *= 2
384265
left = bound // 2
385266
right = min(bound, len(sorted_collection) - 1)
386-
last_result = binary_search_by_recursion(
387-
sorted_collection=sorted_collection, item=item, left=left, right=right
388-
)
389-
if last_result is None:
390-
return -1
391-
return last_result
267+
return binary_search_by_recursion(sorted_collection, item, left, right)
392268

393269

394-
searches = ( # Fastest to slowest...
270+
searches = (
395271
binary_search_std_lib,
396272
binary_search,
397273
exponential_search,
@@ -406,7 +282,7 @@ def exponential_search(sorted_collection: list[int], item: int) -> int:
406282
doctest.testmod()
407283
for search in searches:
408284
name = f"{search.__name__:>26}"
409-
print(f"{name}: {search([0, 5, 7, 10, 15], 10) = }") # type: ignore[operator]
285+
print(f"{name}: {search([0, 5, 7, 10, 15], 10) = }")
410286

411287
print("\nBenchmarks...")
412288
setup = "collection = range(1000)"
@@ -417,13 +293,4 @@ def exponential_search(sorted_collection: list[int], item: int) -> int:
417293
timeit.timeit(
418294
f"{name}(collection, 500)", setup=setup, number=5_000, globals=globals()
419295
),
420-
)
421-
422-
user_input = input("\nEnter numbers separated by comma: ").strip()
423-
collection = sorted(int(item) for item in user_input.split(","))
424-
target = int(input("Enter a single number to be found in the list: "))
425-
result = binary_search(sorted_collection=collection, item=target)
426-
if result == -1:
427-
print(f"{target} was not found in {collection}.")
428-
else:
429-
print(f"{target} was found at position {result} of {collection}.")
296+
)

0 commit comments

Comments
 (0)