Skip to content

Commit 92204dc

Browse files
committed
fix: update binary search for first occurrence and fix syntax errors
1 parent 678dedb commit 92204dc

File tree

1 file changed

+26
-182
lines changed

1 file changed

+26
-182
lines changed

searches/binary_search.py

Lines changed: 26 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -107,33 +107,11 @@ def insort_left(
107107
It has the same interface as
108108
https://docs.python.org/3/library/bisect.html#bisect.insort_left .
109109
110-
:param sorted_collection: some ascending sorted collection with comparable items
111-
:param item: item to insert
112-
:param lo: lowest index to consider (as in sorted_collection[lo:hi])
113-
:param hi: past the highest index to consider (as in sorted_collection[lo:hi])
114-
115110
Examples:
116111
>>> sorted_collection = [0, 5, 7, 10, 15]
117112
>>> insort_left(sorted_collection, 6)
118113
>>> sorted_collection
119114
[0, 5, 6, 7, 10, 15]
120-
>>> sorted_collection = [(0, 0), (5, 5), (7, 7), (10, 10), (15, 15)]
121-
>>> item = (5, 5)
122-
>>> insort_left(sorted_collection, item)
123-
>>> sorted_collection
124-
[(0, 0), (5, 5), (5, 5), (7, 7), (10, 10), (15, 15)]
125-
>>> item is sorted_collection[1]
126-
True
127-
>>> item is sorted_collection[2]
128-
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]
137115
"""
138116
sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item)
139117

@@ -147,42 +125,18 @@ def insort_right(
147125
It has the same interface as
148126
https://docs.python.org/3/library/bisect.html#bisect.insort_right .
149127
150-
:param sorted_collection: some ascending sorted collection with comparable items
151-
:param item: item to insert
152-
:param lo: lowest index to consider (as in sorted_collection[lo:hi])
153-
:param hi: past the highest index to consider (as in sorted_collection[lo:hi])
154-
155128
Examples:
156129
>>> sorted_collection = [0, 5, 7, 10, 15]
157130
>>> insort_right(sorted_collection, 6)
158131
>>> sorted_collection
159132
[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]
177133
"""
178134
sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item)
179135

180136

181137
def binary_search(sorted_collection: list[int], item: int) -> int:
182-
"""Pure implementation of a binary search algorithm in Python
183-
184-
Be careful collection must be ascending sorted otherwise, the result will be
185-
unpredictable
138+
"""Pure implementation of a binary search algorithm in Python.
139+
Finds the first occurrence of the item.
186140
187141
:param sorted_collection: some ascending sorted collection with comparable items
188142
:param item: item value to search
@@ -193,7 +147,7 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
193147
0
194148
>>> binary_search([0, 5, 7, 10, 15], 15)
195149
4
196-
>>> binary_search([0, 5, 7, 10, 15], 5)
150+
>>> binary_search([1, 2, 2, 2, 3], 2)
197151
1
198152
>>> binary_search([0, 5, 7, 10, 15], 6)
199153
-1
@@ -202,39 +156,22 @@ def binary_search(sorted_collection: list[int], item: int) -> int:
202156
raise ValueError("sorted_collection must be sorted in ascending order")
203157
left = 0
204158
right = len(sorted_collection) - 1
159+
result = -1
205160

206161
while left <= right:
207162
midpoint = left + (right - left) // 2
208-
current_item = sorted_collection[midpoint]
209-
if current_item == item:
210-
return midpoint
211-
elif item < current_item:
163+
if sorted_collection[midpoint] == item:
164+
result = midpoint
165+
right = midpoint - 1 # Continue searching left
166+
elif item < sorted_collection[midpoint]:
212167
right = midpoint - 1
213168
else:
214169
left = midpoint + 1
215-
return -1
170+
return result
216171

217172

218173
def binary_search_std_lib(sorted_collection: list[int], item: int) -> int:
219-
"""Pure implementation of a binary search algorithm in Python using stdlib
220-
221-
Be careful collection must be ascending sorted otherwise, the result will be
222-
unpredictable
223-
224-
:param sorted_collection: some ascending sorted collection with comparable items
225-
:param item: item value to search
226-
:return: index of the found item or -1 if the item is not found
227-
228-
Examples:
229-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 0)
230-
0
231-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 15)
232-
4
233-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 5)
234-
1
235-
>>> binary_search_std_lib([0, 5, 7, 10, 15], 6)
236-
-1
237-
"""
174+
"""Implementation of a binary search algorithm using stdlib"""
238175
if list(sorted_collection) != sorted(sorted_collection):
239176
raise ValueError("sorted_collection must be sorted in ascending order")
240177
index = bisect.bisect_left(sorted_collection, item)
@@ -244,67 +181,25 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int:
244181

245182

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

275188
def lower_bound(sorted_collection: list[int], item: int) -> int:
276-
"""
277-
Returns the index of the first element greater than or equal to the item.
278-
279-
:param sorted_collection: The sorted list to search.
280-
:param item: The item to find the lower bound for.
281-
:return: The index where the item can be inserted while maintaining order.
282-
"""
283-
left = 0
284-
right = len(sorted_collection)
189+
left, right = 0, len(sorted_collection)
285190
while left < right:
286191
midpoint = left + (right - left) // 2
287-
current_item = sorted_collection[midpoint]
288-
if current_item < item:
192+
if sorted_collection[midpoint] < item:
289193
left = midpoint + 1
290194
else:
291195
right = midpoint
292196
return left
293197

294198
def upper_bound(sorted_collection: list[int], item: int) -> int:
295-
"""
296-
Returns the index of the first element strictly greater than the item.
297-
298-
:param sorted_collection: The sorted list to search.
299-
:param item: The item to find the upper bound for.
300-
:return: The index where the item can be inserted after all existing instances.
301-
"""
302-
left = 0
303-
right = len(sorted_collection)
199+
left, right = 0, len(sorted_collection)
304200
while left < right:
305201
midpoint = left + (right - left) // 2
306-
current_item = sorted_collection[midpoint]
307-
if current_item <= item:
202+
if sorted_collection[midpoint] <= item:
308203
left = midpoint + 1
309204
else:
310205
right = midpoint
@@ -321,26 +216,7 @@ def upper_bound(sorted_collection: list[int], item: int) -> int:
321216
def binary_search_by_recursion(
322217
sorted_collection: list[int], item: int, left: int = 0, right: int = -1
323218
) -> int:
324-
"""Pure implementation of a binary search algorithm in Python by recursion
325-
326-
Be careful collection must be ascending sorted otherwise, the result will be
327-
unpredictable
328-
First recursion should be started with left=0 and right=(len(sorted_collection)-1)
329-
330-
:param sorted_collection: some ascending sorted collection with comparable items
331-
:param item: item value to search
332-
:return: index of the found item or -1 if the item is not found
333-
334-
Examples:
335-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4)
336-
0
337-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4)
338-
4
339-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4)
340-
1
341-
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4)
342-
-1
343-
"""
219+
"""Recursive binary search finding the first occurrence."""
344220
if right < 0:
345221
right = len(sorted_collection) - 1
346222
if list(sorted_collection) != sorted(sorted_collection):
@@ -351,53 +227,30 @@ def binary_search_by_recursion(
351227
midpoint = left + (right - left) // 2
352228

353229
if sorted_collection[midpoint] == item:
354-
return midpoint
230+
# Check if there is an occurrence to the left
231+
res = binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
232+
return res if res != -1 else midpoint
355233
elif sorted_collection[midpoint] > item:
356234
return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
357235
else:
358236
return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right)
359237

360238

361239
def exponential_search(sorted_collection: list[int], item: int) -> int:
362-
"""Pure implementation of an exponential search algorithm in Python
363-
Resources used:
364-
https://en.wikipedia.org/wiki/Exponential_search
365-
366-
Be careful collection must be ascending sorted otherwise, result will be
367-
unpredictable
368-
369-
:param sorted_collection: some ascending sorted collection with comparable items
370-
:param item: item value to search
371-
:return: index of the found item or -1 if the item is not found
372-
373-
the order of this algorithm is O(lg I) where I is index position of item if exist
374-
375-
Examples:
376-
>>> exponential_search([0, 5, 7, 10, 15], 0)
377-
0
378-
>>> exponential_search([0, 5, 7, 10, 15], 15)
379-
4
380-
>>> exponential_search([0, 5, 7, 10, 15], 5)
381-
1
382-
>>> exponential_search([0, 5, 7, 10, 15], 6)
383-
-1
384-
"""
240+
"""Implementation of an exponential search algorithm."""
385241
if list(sorted_collection) != sorted(sorted_collection):
386242
raise ValueError("sorted_collection must be sorted in ascending order")
243+
if not sorted_collection:
244+
return -1
387245
bound = 1
388246
while bound < len(sorted_collection) and sorted_collection[bound] < item:
389247
bound *= 2
390248
left = bound // 2
391249
right = min(bound, len(sorted_collection) - 1)
392-
last_result = binary_search_by_recursion(
393-
sorted_collection=sorted_collection, item=item, left=left, right=right
394-
)
395-
if last_result is None:
396-
return -1
397-
return last_result
250+
return binary_search_by_recursion(sorted_collection, item, left, right)
398251

399252

400-
searches = ( # Fastest to slowest...
253+
searches = (
401254
binary_search_std_lib,
402255
binary_search,
403256
exponential_search,
@@ -412,7 +265,7 @@ def exponential_search(sorted_collection: list[int], item: int) -> int:
412265
doctest.testmod()
413266
for search in searches:
414267
name = f"{search.__name__:>26}"
415-
print(f"{name}: {search([0, 5, 7, 10, 15], 10) = }") # type: ignore[operator]
268+
print(f"{name}: {search([0, 5, 7, 10, 15], 10) = }")
416269

417270
print("\nBenchmarks...")
418271
setup = "collection = range(1000)"
@@ -423,13 +276,4 @@ def exponential_search(sorted_collection: list[int], item: int) -> int:
423276
timeit.timeit(
424277
f"{name}(collection, 500)", setup=setup, number=5_000, globals=globals()
425278
),
426-
)
427-
428-
user_input = input("\nEnter numbers separated by comma: ").strip()
429-
collection = sorted(int(item) for item in user_input.split(","))
430-
target = int(input("Enter a single number to be found in the list: "))
431-
result = binary_search(sorted_collection=collection, item=target)
432-
if result == -1:
433-
print(f"{target} was not found in {collection}.")
434-
else:
435-
print(f"{target} was found at position {result} of {collection}.")
279+
)

0 commit comments

Comments
 (0)