AK+LibJS+LibWeb: Make binary_search() return the first occurrence

Given `{1, 2, 2, 2, 2, 2, 2, 3}` and the needle being `2`, the function
used to return the first matching element that it found (at index 3). It
now continues the search until it founds the first one, here at index 1.

Note that one side effect of that change is that when the binary search
fails, `nearby_index` now gives you the index right after instead of the
one before. The changes in LibJS and LibWeb are needed because of that.

Co-authored-by: Nico Weber <[email protected]>
This commit is contained in:
Lucas CHOLLETandNico Weber authored and Nico Weber committed 2025-04-13 17:42:52 -04:00
1 parent 0954c95a5c
commit d012289263
4 files changed
+28 -18

No files matched your search

+7 -16
View File
@@ -41,28 +41,19 @@ constexpr auto binary_search(
size_t low = 0;
size_t high = haystack.size() - 1;
while (low <= high) {
while (low != high) {
size_t middle = low + (high - low) / 2;
int comparison = comparator(needle, haystack[middle]);
if (comparison < 0)
if (middle != 0)
high = middle - 1;
else
break;
else if (comparison > 0)
if (comparison <= 0)
high = middle;
else
low = middle + 1;
else {
if (nearby_index)
*nearby_index = middle;
return &haystack[middle];
}
}
if (nearby_index)
*nearby_index = min(low, high);
*nearby_index = high;
if (comparator(needle, haystack[high]) == 0)
return &haystack[high];
return nullptr;
}
+13
View File
@@ -120,6 +120,19 @@ TEST_CASE(unsigned_to_signed_regression)
EXPECT_EQ(nearby_index, 1u);
}
TEST_CASE(always_find_first_occurrence)
{
Array const input { 1, 2, 2, 2, 2, 2, 2, 3 };
size_t nearby_index {};
EXPECT_EQ(binary_search(input, 1, &nearby_index), input.data());
EXPECT_EQ(nearby_index, 0u);
EXPECT_EQ(binary_search(input, 2, &nearby_index), input.data() + 1);
EXPECT_EQ(nearby_index, 1u);
EXPECT_EQ(binary_search(input, 3, &nearby_index), input.data() + 7);
EXPECT_EQ(nearby_index, 7u);
}
RANDOMIZED_TEST_CASE(finds_number_that_is_present)
{
GEN(vec, Gen::vector(1, 16, []() { return Gen::number_u64(); }));
+5
View File
@@ -87,6 +87,8 @@ SourceRange SourceCode::range_from_offsets(u32 start_offset, u32 end_offset) con
Position current { .line = 1, .column = 1, .offset = 0 };
if (!m_cached_positions.is_empty()) {
// FIXME: Use AK::lower_bound instead of relying on the behavior
// of nearby_index.
Position const dummy;
size_t nearest_index = 0;
binary_search(m_cached_positions, dummy, &nearest_index,
@@ -94,6 +96,9 @@ SourceRange SourceCode::range_from_offsets(u32 start_offset, u32 end_offset) con
return start_offset - starting_point.offset;
});
if (nearest_index > 0 && m_cached_positions[nearest_index].offset != start_offset)
nearest_index--;
current = m_cached_positions[nearest_index];
}
@@ -156,8 +156,9 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
}
// We have two samples on either side of the x value we want, so we can linearly interpolate between them.
auto& sample1 = cached_x_samples[nearby_index];
auto& sample2 = cached_x_samples[nearby_index + 1];
VERIFY(nearby_index > 0);
auto& sample1 = cached_x_samples[nearby_index - 1];
auto& sample2 = cached_x_samples[nearby_index];
auto factor = (x - sample1.x) / (sample2.x - sample1.x);
return sample1.y + factor * (sample2.y - sample1.y);
},