AK: Add Vector::remove_all(container)/remove_all(it, end)

Instead of repeatedly removing elements off the vector, this allows for
specifying all the removed indices at once, and does not perform any
extra reallocations or unnecessary moves.
This commit is contained in:
Ali Mohammad Pur 2025-09-10 06:10:28 +02:00 committed by Ali Mohammad Pur
parent 93d7efa4c3
commit 80e5356853
2 changed files with 89 additions and 0 deletions

View File

@ -503,6 +503,44 @@ public:
update_metadata();
}
/// The iterator pair identify a set of indices *in ascending order* to be removed.
template<typename It, IteratorPairWith<It> EndIt, typename ToIndex = decltype (*declval<It>()) (*)(It const&)>
void remove_all(It&& it, EndIt const& end, ToIndex const& to_index = [](It const& it) -> decltype(auto) { return *it; })
{
if (!(it != end))
return;
size_t write_index = to_index(it);
VERIFY(write_index < m_size);
raw_at(write_index).~StorageType();
++it;
size_t next_remove_index = it != end ? to_index(it) : m_size;
for (auto read_index = write_index + 1; read_index < m_size; ++read_index) {
if (read_index == next_remove_index) {
raw_at(read_index).~StorageType();
++it;
next_remove_index = it != end ? to_index(it) : m_size;
} else {
if constexpr (Traits<StorageType>::is_trivial()) {
__builtin_memcpy(slot(write_index), slot(read_index), sizeof(StorageType));
} else {
new (slot(write_index)) StorageType(move(raw_at(read_index)));
raw_at(read_index).~StorageType();
}
++write_index;
}
}
VERIFY(!(it != end));
m_size = write_index;
update_metadata();
}
template<IterableContainer Is, typename ToIndex = decltype (*declval<Is>().begin()) (*)(decltype(declval<Is>().begin()) const&)>
void remove_all(Is&& is, ToIndex const& to_index = [](auto const& it) -> decltype(auto) { return *it; })
{
remove_all(is.begin(), is.end(), to_index);
}
template<typename TUnaryPredicate>
bool remove_first_matching(TUnaryPredicate const& predicate)
{

View File

@ -643,6 +643,57 @@ static bool is_inline_element(auto& el, auto& vector)
\
for (auto& el : v) \
EXPECT(is_inline_element(el, v)); \
} \
TEST_CASE(Vector##_remove_all) \
{ \
{ \
Vector<int> v0 { 1, 2, 3, 2, 4, 2, 5 }; \
Array indices { 1, 4, 6 }; \
v0.remove_all(indices); \
EXPECT_EQ(v0.size(), 4u); \
EXPECT_EQ(v0[0], 1); \
EXPECT_EQ(v0[1], 3); \
EXPECT_EQ(v0[2], 2); \
EXPECT_EQ(v0[3], 2); \
} \
{ \
Vector<int> v1 { 1, 2, 3, 4, 5 }; \
Array indices { 0, 1, 2, 3, 4 }; \
v1.remove_all(indices); \
EXPECT_EQ(v1.size(), 0u); \
} \
{ \
Vector<int> v2 { 1, 2, 3, 4, 5 }; \
Array<u32, 0> indices; \
v2.remove_all(indices); \
EXPECT_EQ(v2.size(), 5u); \
EXPECT_EQ(v2[0], 1); \
EXPECT_EQ(v2[1], 2); \
EXPECT_EQ(v2[2], 3); \
EXPECT_EQ(v2[3], 4); \
EXPECT_EQ(v2[4], 5); \
} \
{ \
Vector<int> v3; \
Array<u32, 0> indices; \
v3.remove_all(indices); \
EXPECT_EQ(v3.size(), 0u); \
} \
/* One more test with a nonstandard iterator deref function */ \
{ \
Vector<int> v4 { 1, 2, 3, 2, 4, 2, 5 }; \
struct IndexWrapper { \
size_t index; \
size_t const& operator*() const { return index; } \
}; \
Array<IndexWrapper, 3> indices { IndexWrapper { 1 }, IndexWrapper { 4 }, IndexWrapper { 6 } }; \
v4.remove_all(indices, [](auto const& it) -> size_t { return **it; }); \
EXPECT_EQ(v4.size(), 4u); \
EXPECT_EQ(v4[0], 1); \
EXPECT_EQ(v4[1], 3); \
EXPECT_EQ(v4[2], 2); \
EXPECT_EQ(v4[3], 2); \
} \
}
DECLARE_TESTS_FOR_VEC(Vector)