@@ -243,6 +243,81 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int:
243243 return - 1
244244
245245
246+ 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+ """
272+ if list (sorted_collection ) != sorted (sorted_collection ):
273+ raise ValueError ("sorted_collection must be sorted in ascending order" )
274+
275+ 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 )
285+ while left < right :
286+ midpoint = left + (right - left ) // 2
287+ current_item = sorted_collection [midpoint ]
288+ if current_item < item :
289+ left = midpoint + 1
290+ else :
291+ right = midpoint
292+ return left
293+
294+ 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 )
304+ while left < right :
305+ midpoint = left + (right - left ) // 2
306+ current_item = sorted_collection [midpoint ]
307+ if current_item <= item :
308+ left = midpoint + 1
309+ else :
310+ right = midpoint
311+ return left
312+
313+ left = lower_bound (sorted_collection , item )
314+ right = upper_bound (sorted_collection , item )
315+
316+ if left == len (sorted_collection ) or sorted_collection [left ] != item :
317+ return []
318+ return list (range (left , right ))
319+
320+
246321def binary_search_by_recursion (
247322 sorted_collection : list [int ], item : int , left : int = 0 , right : int = - 1
248323) -> int :
0 commit comments