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.
This commit is contained in:
Andreas Kling 2026-05-11 08:42:51 +02:00 committed by Andreas Kling
parent 2dab9f53d2
commit 1859ecbb24
2 changed files with 68 additions and 0 deletions

View file

@ -25,6 +25,27 @@ struct DefaultComparator {
}
};
template<typename Container, typename Needle, typename Comparator = DefaultComparator>
[[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<typename Container, typename Needle, typename Comparator = DefaultComparator>
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

View file

@ -108,6 +108,52 @@ TEST_CASE(constexpr_array_search)
static_assert(binary_search(array, 3) == nullptr);
}
TEST_CASE(lower_bound_index)
{
Array<int, 7> 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<int> vector;
EXPECT_EQ(lower_bound_index(vector, 1), 0u);
}
TEST_CASE(lower_bound_index_custom_comparator)
{
Vector<ByteString> 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<int, 3> 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<u32, 5> const input { 0, 1, 2, 3, 4 };