From 1859ecbb24b7ea2de8ca66395ae975a69ce80cd6 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 11 May 2026 08:42:51 +0200 Subject: [PATCH] AK: Add lower_bound_index to binary search helpers Add a helper for finding the first position in a sorted container where needle can be inserted while preserving sort order. This gives callers a lower-bound insertion point. Cover empty inputs, duplicate values, custom comparators, and constexpr use in TestBinarySearch. --- AK/BinarySearch.h | 22 +++++++++++++++++ Tests/AK/TestBinarySearch.cpp | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/AK/BinarySearch.h b/AK/BinarySearch.h index de2dbe299a..14956865af 100644 --- a/AK/BinarySearch.h +++ b/AK/BinarySearch.h @@ -25,6 +25,27 @@ struct DefaultComparator { } }; +template +[[nodiscard]] constexpr size_t lower_bound_index( + Container&& haystack, + Needle&& needle, + Comparator comparator = Comparator {}) +{ + size_t low = 0; + size_t high = haystack.size(); + + while (low < high) { + size_t middle = low + (high - low) / 2; + + if (comparator(haystack[middle], needle) < 0) + low = middle + 1; + else + high = middle; + } + + return low; +} + template constexpr auto binary_search( Container&& haystack, @@ -69,4 +90,5 @@ constexpr auto binary_search( #if USING_AK_GLOBALLY using AK::binary_search; +using AK::lower_bound_index; #endif diff --git a/Tests/AK/TestBinarySearch.cpp b/Tests/AK/TestBinarySearch.cpp index 8e7bb3239f..d2ed30abcb 100644 --- a/Tests/AK/TestBinarySearch.cpp +++ b/Tests/AK/TestBinarySearch.cpp @@ -108,6 +108,52 @@ TEST_CASE(constexpr_array_search) static_assert(binary_search(array, 3) == nullptr); } +TEST_CASE(lower_bound_index) +{ + Array array { 1, 3, 3, 3, 7, 9, 11 }; + + EXPECT_EQ(lower_bound_index(array, 0), 0u); + EXPECT_EQ(lower_bound_index(array, 1), 0u); + EXPECT_EQ(lower_bound_index(array, 2), 1u); + EXPECT_EQ(lower_bound_index(array, 3), 1u); + EXPECT_EQ(lower_bound_index(array, 4), 4u); + EXPECT_EQ(lower_bound_index(array, 12), 7u); +} + +TEST_CASE(lower_bound_index_empty) +{ + Vector vector; + + EXPECT_EQ(lower_bound_index(vector, 1), 0u); +} + +TEST_CASE(lower_bound_index_custom_comparator) +{ + Vector strings; + strings.append("bat"); + strings.append("cat"); + strings.append("dog"); + + auto string_compare = [](ByteString const& a, ByteString const& b) -> int { + return strcmp(a.characters(), b.characters()); + }; + + EXPECT_EQ(lower_bound_index(strings, ByteString("ant"), string_compare), 0u); + EXPECT_EQ(lower_bound_index(strings, ByteString("cat"), string_compare), 1u); + EXPECT_EQ(lower_bound_index(strings, ByteString("cow"), string_compare), 2u); + EXPECT_EQ(lower_bound_index(strings, ByteString("elk"), string_compare), 3u); +} + +TEST_CASE(constexpr_lower_bound_index) +{ + constexpr Array array = { 1, 17, 42 }; + + static_assert(lower_bound_index(array, 0) == 0); + static_assert(lower_bound_index(array, 17) == 1); + static_assert(lower_bound_index(array, 18) == 2); + static_assert(lower_bound_index(array, 43) == 3); +} + TEST_CASE(unsigned_to_signed_regression) { Array const input { 0, 1, 2, 3, 4 };