@@ -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
181137def 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
218173def 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
246183def 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:
321216def 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
361239def 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 ("\n Benchmarks..." )
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 ("\n Enter 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