From ad1574f64cf5c7ef16ab637d855166cf4bbfe206 Mon Sep 17 00:00:00 2001 From: Alan718_mac Date: Sat, 22 Nov 2025 18:00:25 -0500 Subject: [PATCH 1/6] Fix binary search with duplicates issue #13886 --- searches/binary_search.py | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/searches/binary_search.py b/searches/binary_search.py index 2e66b672d5b4..519b9c36d936 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -243,6 +243,71 @@ def binary_search_std_lib(sorted_collection: list[int], item: int) -> int: return -1 +def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> list[int]: + """Pure implementation of a binary search algorithm in Python that supports + duplicates. + + Resources used: + https://stackoverflow.com/questions/13197552/using-binary-search-with-sorted-array-with-duplicates + + The collection must be sorted in ascending order; otherwise the result will be + unpredictable. If the target appears multiple times, this function returns a + list of **all** indexes where the target occurs. If the target is not found, + it returns an empty list. + + :param sorted_collection: some ascending sorted collection with comparable items + :param item: item value to search for + :return: a list of indexes where the item is found (empty list if not found) + + Examples: + >>> binary_search_with_duplicates([0, 5, 7, 10, 15], 0) + [0] + >>> binary_search_with_duplicates([0, 5, 7, 10, 15], 15) + [4] + >>> binary_search_with_duplicates([1, 2, 2, 2, 3], 2) + [1, 2, 3] + >>> binary_search_with_duplicates([1, 2, 2, 2, 3], 4) + [] + """ + if list(sorted_collection) != sorted(sorted_collection): + raise ValueError("sorted_collection must be sorted in ascending order") + + """find lower bounds""" + + def lower_bound(sorted_collection: list[int], item: int) -> int: + left = 0 + right = len(sorted_collection) + while left < right: + midpoint = left + (right - left) // 2 + current_item = sorted_collection[midpoint] + if current_item < item: + left = midpoint + 1 + else: + right = midpoint + return left + + """find upper bounds""" + + def upper_bound(sorted_collection: list[int], item: int) -> int: + left = 0 + right = len(sorted_collection) + while left < right: + midpoint = left + (right - left) // 2 + current_item = sorted_collection[midpoint] + if current_item <= item: + left = midpoint + 1 + else: + right = midpoint + return left + + left = lower_bound(sorted_collection, item) + right = upper_bound(sorted_collection, item) + + if left == len(sorted_collection) or sorted_collection[left] != item: + return [] + return list(range(left, right)) + + def binary_search_by_recursion( sorted_collection: list[int], item: int, left: int = 0, right: int = -1 ) -> int: From 8af1e5b827f8d32f305310413f949de205eb2341 Mon Sep 17 00:00:00 2001 From: Alan718 <75001847+shuhao-alan-fan@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:38:22 -0500 Subject: [PATCH 2/6] Add docstrings to binary search functions Added docstrings for lower_bound and upper_bound functions. --- searches/binary_search.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 519b9c36d936..4dffc2d860a3 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -272,9 +272,19 @@ def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> li if list(sorted_collection) != sorted(sorted_collection): raise ValueError("sorted_collection must be sorted in ascending order") - """find lower bounds""" + def lower_bound(sorted_collection: list[int], item: int) -> int: + """ + Returns the index of the first element greater than or equal to the item. + + Args: + sorted_collection: The sorted list to search. + item: The item to find the lower bound for. + + Returns: + int: The index where the item can be inserted while maintaining order. + """ left = 0 right = len(sorted_collection) while left < right: @@ -286,9 +296,19 @@ def lower_bound(sorted_collection: list[int], item: int) -> int: right = midpoint return left - """find upper bounds""" + def upper_bound(sorted_collection: list[int], item: int) -> int: + """ + Returns the index of the first element strictly greater than the item. + + Args: + sorted_collection: The sorted list to search. + item: The item to find the upper bound for. + + Returns: + int: The index where the item can be inserted after all existing instances. + """ left = 0 right = len(sorted_collection) while left < right: From b40592894f3817124e1c38403123cea97666a064 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:38:47 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- searches/binary_search.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 4dffc2d860a3..8d880826c451 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -272,12 +272,10 @@ def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> li if list(sorted_collection) != sorted(sorted_collection): raise ValueError("sorted_collection must be sorted in ascending order") - - def lower_bound(sorted_collection: list[int], item: int) -> int: """ Returns the index of the first element greater than or equal to the item. - + Args: sorted_collection: The sorted list to search. item: The item to find the lower bound for. @@ -296,12 +294,10 @@ def lower_bound(sorted_collection: list[int], item: int) -> int: right = midpoint return left - - def upper_bound(sorted_collection: list[int], item: int) -> int: """ Returns the index of the first element strictly greater than the item. - + Args: sorted_collection: The sorted list to search. item: The item to find the upper bound for. From 58ad165d9709b9328c4ab2cd69908de18ba939e4 Mon Sep 17 00:00:00 2001 From: Alan718 <75001847+shuhao-alan-fan@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:23:19 -0500 Subject: [PATCH 4/6] Update searches/binary_search.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- searches/binary_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index 8d880826c451..c5863ec354a2 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -252,7 +252,7 @@ def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> li The collection must be sorted in ascending order; otherwise the result will be unpredictable. If the target appears multiple times, this function returns a - list of **all** indexes where the target occurs. If the target is not found, + list of all indexes where the target occurs. If the target is not found, it returns an empty list. :param sorted_collection: some ascending sorted collection with comparable items From f16b1d252e1a56cf71e7a6caa54c52bb2f8f774e Mon Sep 17 00:00:00 2001 From: Alan718 <75001847+shuhao-alan-fan@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:26:36 -0500 Subject: [PATCH 5/6] Refactor docstrings for lower_bound and upper_bound Updated docstring parameter and return type annotations for lower_bound and upper_bound functions. --- searches/binary_search.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index c5863ec354a2..cb1d3d5fc775 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -276,12 +276,9 @@ def lower_bound(sorted_collection: list[int], item: int) -> int: """ Returns the index of the first element greater than or equal to the item. - Args: - sorted_collection: The sorted list to search. - item: The item to find the lower bound for. - - Returns: - int: The index where the item can be inserted while maintaining order. + :param sorted_collection: The sorted list to search. + :param item: The item to find the lower bound for. + :return: The index where the item can be inserted while maintaining order. """ left = 0 right = len(sorted_collection) @@ -298,12 +295,9 @@ def upper_bound(sorted_collection: list[int], item: int) -> int: """ Returns the index of the first element strictly greater than the item. - Args: - sorted_collection: The sorted list to search. - item: The item to find the upper bound for. - - Returns: - int: The index where the item can be inserted after all existing instances. + :param sorted_collection: The sorted list to search. + :param item: The item to find the upper bound for. + :return: The index where the item can be inserted after all existing instances. """ left = 0 right = len(sorted_collection) From 2d4dfe1746893dd60dd9bdf14861d6f249f62c10 Mon Sep 17 00:00:00 2001 From: John Law Date: Sat, 13 Dec 2025 00:50:16 +0000 Subject: [PATCH 6/6] Update searches/binary_search.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- searches/binary_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searches/binary_search.py b/searches/binary_search.py index cb1d3d5fc775..5125dc6bdb9a 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -253,7 +253,7 @@ def binary_search_with_duplicates(sorted_collection: list[int], item: int) -> li The collection must be sorted in ascending order; otherwise the result will be unpredictable. If the target appears multiple times, this function returns a list of all indexes where the target occurs. If the target is not found, - it returns an empty list. + this function returns an empty list. :param sorted_collection: some ascending sorted collection with comparable items :param item: item value to search for