Loading libs/androidfw/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -231,6 +231,7 @@ cc_test { "tests/ResourceTimer_test.cpp", "tests/ResourceUtils_test.cpp", "tests/ResTable_test.cpp", "tests/sorted_vector_set_test.cpp", "tests/Split_test.cpp", "tests/StringPiece_test.cpp", "tests/StringPool_test.cpp", Loading libs/androidfw/include/androidfw/LoadedArsc.h +2 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ #include "androidfw/Chunk.h" #include "androidfw/Idmap.h" #include "androidfw/ResourceTypes.h" #include "androidfw/sorted_vector_set.h" #include "androidfw/Util.h" namespace android { Loading Loading @@ -238,7 +239,7 @@ class LoadedPackage { // Populates a set of strings representing locales. // If `canonicalize` is set to true, each locale is transformed into its canonical format // before being inserted into the set. This may cause some equivalent locales to de-dupe. using Locales = std::set<std::string, std::less<>>; using Locales = sorted_vector_set<std::string>; void CollectLocales(bool canonicalize, Locales* out_locales) const; // type_idx is TT - 1 from 0xPPTTEEEE. Loading libs/androidfw/include/androidfw/sorted_vector_set.h 0 → 100644 +204 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <algorithm> #include <concepts> #include <utility> #include <vector> namespace android { // The implementation of std::set that holds all items in a sorted vector - basically std::flat_set // with a little fewer generic features in favor of code complexity. // Given that libc++ decided to not implement it for now, this class can be used until it's // available in the STL. // // This is the most efficient way of storing a rarely modified collection of items - all the // lookups happen faster because of the cache locality, there's much less per-iterm memory overhead, // and in order iteration is the fastest it can be on modern hardware. // // The tradeoff is in the insertion and erasure complexity - this container has to move the whole // trail of elements when modifying one in the middle, turning it into a linear operation instead // of a logarithmic one. // // Also, beware of the iterator and pointer stability - the underlying vector can resize at any // insertion, so insertions invalidate all iterators and pointers, and removals invalidate // everything past the removed element. // // TODO(b/406585404): replace with std::flat_set<> when available. // A way to ensure that the templated lookup functions are only called with the compatible types. template <class T, class U, class Cmp> concept Comparable = requires(const T t, const U u, Cmp c) { { c(t, u) } -> std::convertible_to<bool>; { c(u, t) } -> std::convertible_to<bool>; }; template <class T, class Cmp = std::less<>> class sorted_vector_set : std::vector<T> { using base = std::vector<T>; public: // Set doesn't let you modify its elements via iterators. using iterator = typename base::const_iterator; using reverse_iterator = typename base::const_reverse_iterator; using const_iterator = iterator; using const_reverse_iterator = reverse_iterator; using value_type = typename base::value_type; using reference = const value_type&; using size_type = typename base::size_type; using key_compare = Cmp; using value_compare = Cmp; sorted_vector_set() = default; explicit sorted_vector_set(Cmp cmp) : mCmp(std::move(cmp)) { } sorted_vector_set(size_t reserve_size) { base::reserve(reserve_size); } // Const functions mostly can be passed through. using base::capacity; using base::cbegin; using base::cend; using base::crbegin; using base::crend; using base::empty; using base::size; // These are totally safe as well. using base::clear; using base::erase; using base::pop_back; using base::reserve; using base::shrink_to_fit; iterator begin() const { return cbegin(); } iterator end() const { return cend(); } reverse_iterator rbegin() const { return crbegin(); } reverse_iterator rend() const { return crend(); } key_compare key_comp() const { return mCmp; } value_compare value_comp() const { return mCmp; } // Add the vector interface accessor as well. const base& vector() const { return *this; } // Now, set interface. template <class Key = T> requires Comparable<Key, T, Cmp> bool contains(const Key& k) const { return find(k) != base::cend(); } template <class Key = T> requires Comparable<Key, T, Cmp> const_iterator find(const Key& k) const { auto it = lower_bound(k); if (it == base::cend()) return it; if (mCmp(k, *it)) return base::end(); return it; } template <class Key = T> requires Comparable<Key, T, Cmp> const_iterator lower_bound(const Key& k) const { return std::lower_bound(base::begin(), base::end(), k, mCmp); } std::pair<iterator, bool> insert(const T& t) { return insert(T(t)); } std::pair<iterator, bool> insert(T&& t) { auto it = lower_bound(t); if (it != base::cend() && !mCmp(t, *it)) { return {it, false}; } return {base::insert(it, std::move(t)), true}; } template <class Key = T> requires Comparable<Key, T, Cmp> std::pair<iterator, bool> emplace(Key&& k) { auto it = lower_bound(k); if (it != base::cend() && !mCmp(k, *it)) { return {it, false}; } return {base::emplace(it, std::forward<Key>(k)), true}; } template <class Key = T> requires Comparable<Key, T, Cmp> std::pair<iterator, bool> emplace_hint(iterator hint, Key&& k) { // Check if the hint is in the correct position. if ((hint != base::cend() && mCmp(*hint, k)) || (hint != base::cbegin() && mCmp(k, *(std::prev(hint))))) { // No, discard it. return emplace(std::forward<Key>(k)); } return emplace_impl(hint, std::forward<Key>(k)); } template <class Key = T> requires Comparable<Key, T, Cmp> size_t count(const Key& k) const { return contains(k); } template <class Key = T> requires Comparable<Key, T, Cmp> size_t erase(const Key& k) { const auto it = find(k); if (it == cend()) { return 0; } base::erase(it); return 1; } private: template <class Key> std::pair<iterator, bool> emplace_impl(iterator pos, Key&& k) { if (pos != base::cend() && !mCmp(k, *pos)) { return {pos, false}; } return {base::emplace(pos, std::forward<Key>(k)), true}; } private: [[no_unique_address]] Cmp mCmp; }; } // namespace android libs/androidfw/tests/sorted_vector_set_test.cpp 0 → 100644 +485 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "androidfw/sorted_vector_set.h" #include <gtest/gtest.h> #include <algorithm> #include <memory> #include <set> #include <utility> #include <vector> namespace android { namespace { template <class SortedVectorContainer> class SortedVectorTestBase : public ::testing::Test { public: void TearDown() override { EXPECT_TRUE(std::ranges::is_sorted(s, s.key_comp())); } protected: SortedVectorContainer s; }; class SortedVectorSetIntTest : public SortedVectorTestBase<sorted_vector_set<int>> {}; class SortedVectorSetIntGreaterTest : public SortedVectorTestBase<sorted_vector_set<int, std::greater<int>>> {}; struct MyStruct { int value; std::string name; bool operator==(const MyStruct& other) const { return value == other.value && name == other.name; } }; struct MyStructComparator { bool operator()(const MyStruct& a, const MyStruct& b) const { return a.value < b.value; // Compare based on value } }; class SortedVectorSetMyStructTest : public SortedVectorTestBase<sorted_vector_set<MyStruct, MyStructComparator>> {}; class SortedVectorSetStringTest : public SortedVectorTestBase<sorted_vector_set<std::string>> {}; } // namespace TEST_F(SortedVectorSetIntTest, DefaultConstructor) { EXPECT_TRUE(this->s.empty()); EXPECT_EQ(this->s.size(), 0); } TEST_F(SortedVectorSetIntTest, SizeConstructor) { sorted_vector_set<int> s2(10); // Reserve space for 10 elements. EXPECT_TRUE(s2.empty()); EXPECT_EQ(s2.size(), 0); EXPECT_GE(s2.capacity(), 10); } TEST_F(SortedVectorSetIntTest, InsertAndFind) { auto result1 = this->s.insert(5); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, 5); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.insert(5); EXPECT_FALSE(result2.second); EXPECT_EQ(*result2.first, 5); EXPECT_EQ(this->s.size(), 1); EXPECT_TRUE(this->s.contains(5)); EXPECT_FALSE(this->s.contains(10)); auto it = this->s.find(5); EXPECT_EQ(*it, 5); auto it2 = this->s.find(10); EXPECT_EQ(it2, this->s.end()); auto it3 = this->s.find(1); EXPECT_EQ(it3, this->s.end()); } TEST_F(SortedVectorSetIntTest, InsertMultipleAndFind) { this->s.insert(30); this->s.insert(10); this->s.insert(20); this->s.insert(50); this->s.insert(40); EXPECT_EQ(this->s.size(), 5); auto it = this->s.begin(); EXPECT_EQ(*it++, 10); EXPECT_EQ(*it++, 20); EXPECT_EQ(*it++, 30); EXPECT_EQ(*it++, 40); EXPECT_EQ(*it, 50); EXPECT_EQ(this->s.find(5), this->s.end()); EXPECT_EQ(this->s.find(15), this->s.end()); EXPECT_EQ(this->s.find(25), this->s.end()); EXPECT_EQ(this->s.find(35), this->s.end()); EXPECT_EQ(this->s.find(45), this->s.end()); EXPECT_EQ(this->s.find(55), this->s.end()); } TEST_F(SortedVectorSetIntTest, Emplace) { auto result1 = this->s.emplace(5); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, 5); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.emplace(5); EXPECT_FALSE(result2.second); EXPECT_EQ(*result2.first, 5); EXPECT_EQ(this->s.size(), 1); } TEST_F(SortedVectorSetIntTest, EmplaceHint) { this->s.insert(1); this->s.insert(3); this->s.insert(6); auto hint = this->s.find(3); auto result1 = this->s.emplace_hint(hint, 4); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, 4); EXPECT_EQ(this->s.size(), 4); auto result2 = this->s.emplace_hint(this->s.begin(), 2); EXPECT_TRUE(result2.second); EXPECT_EQ(*result2.first, 2); EXPECT_EQ(this->s.size(), 5); auto result3 = this->s.emplace_hint(this->s.end(), 10); EXPECT_TRUE(result3.second); EXPECT_EQ(*result3.first, 10); EXPECT_EQ(this->s.size(), 6); auto result4 = this->s.emplace_hint(this->s.find(3), 3); EXPECT_FALSE(result4.second); EXPECT_EQ(*result4.first, 3); EXPECT_EQ(this->s.size(), 6); auto result5 = this->s.emplace_hint(this->s.find(10), 5); EXPECT_TRUE(result5.second); EXPECT_EQ(*result5.first, 5); EXPECT_EQ(this->s.size(), 7); } TEST_F(SortedVectorSetIntTest, EmplaceHintBeginning) { this->s.insert(2); auto hint = this->s.begin(); auto result = this->s.emplace_hint(hint, 0); EXPECT_TRUE(result.second); EXPECT_EQ(*result.first, 0); EXPECT_EQ(this->s.size(), 2); result = this->s.emplace_hint(this->s.end(), 1); EXPECT_TRUE(result.second); EXPECT_EQ(*result.first, 1); EXPECT_EQ(this->s.size(), 3); } TEST_F(SortedVectorSetIntTest, EmplaceHintEnd) { this->s.insert(1); auto hint = this->s.end(); auto result = this->s.emplace_hint(hint, 2); EXPECT_TRUE(result.second); EXPECT_EQ(*result.first, 2); EXPECT_EQ(this->s.size(), 2); } TEST_F(SortedVectorSetIntTest, EmplaceHintExisting) { this->s.insert(1); this->s.insert(2); auto hint = this->s.find(1); auto result = this->s.emplace_hint(hint, 1); EXPECT_FALSE(result.second); EXPECT_EQ(*result.first, 1); EXPECT_EQ(this->s.size(), 2); } TEST_F(SortedVectorSetIntTest, Count) { this->s.insert(5); EXPECT_EQ(this->s.count(5), 1); EXPECT_EQ(this->s.count(10), 0); } TEST_F(SortedVectorSetIntGreaterTest, CustomComparator) { this->s.insert(5); this->s.insert(10); this->s.insert(1); auto it = this->s.begin(); EXPECT_EQ(*it++, 10); EXPECT_EQ(*it++, 5); EXPECT_EQ(*it, 1); } TEST_F(SortedVectorSetMyStructTest, InsertWithCustomComparator) { MyStruct a(1, "one"); MyStruct b(2, "two"); MyStruct c(3, "three"); auto result1 = this->s.insert(a); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, a); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.insert(b); EXPECT_TRUE(result2.second); EXPECT_EQ(*result2.first, b); EXPECT_EQ(this->s.size(), 2); auto result3 = this->s.insert(a); // Duplicate EXPECT_FALSE(result3.second); EXPECT_EQ(*result3.first, a); EXPECT_EQ(this->s.size(), 2); EXPECT_TRUE(this->s.contains(a)); EXPECT_TRUE(this->s.contains(b)); EXPECT_FALSE(this->s.contains(c)); } TEST_F(SortedVectorSetMyStructTest, FindWithCustomComparator) { MyStruct a(1, "one"); MyStruct b(2, "two"); this->s.insert(a); this->s.insert(b); auto it1 = this->s.find(a); EXPECT_EQ(*it1, a); auto it2 = this->s.find(b); EXPECT_EQ(*it2, b); MyStruct c(3, "three"); auto it3 = this->s.find(c); EXPECT_EQ(it3, this->s.end()); } TEST_F(SortedVectorSetMyStructTest, LowerBoundWithCustomComparator) { MyStruct a(1, "one"); MyStruct b(2, "two"); MyStruct c(3, "three"); this->s.insert(a); this->s.insert(c); auto it1 = this->s.lower_bound(b); EXPECT_EQ(*it1, c); // lower_bound of 2 is 3 auto it2 = this->s.lower_bound(a); EXPECT_EQ(*it2, a); } TEST_F(SortedVectorSetMyStructTest, EmplaceWithCustomComparator) { auto result1 = this->s.emplace({1, "one"}); EXPECT_TRUE(result1.second); EXPECT_EQ(result1.first->value, 1); EXPECT_EQ(result1.first->name, "one"); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.emplace({1, "another"}); // Duplicate value EXPECT_FALSE(result2.second); EXPECT_EQ(result2.first->value, 1); EXPECT_EQ(result2.first->name, "one"); // Should not change EXPECT_EQ(this->s.size(), 1); } TEST_F(SortedVectorSetMyStructTest, EmplaceHintWithCustomComparator) { this->s.emplace({1, "one"}); this->s.emplace({3, "three"}); auto hint = this->s.find(MyStruct(3, "three")); auto result1 = this->s.emplace_hint(hint, {2, "two"}); EXPECT_TRUE(result1.second); EXPECT_EQ(result1.first->value, 2); EXPECT_EQ(result1.first->name, "two"); EXPECT_EQ(this->s.size(), 3); auto result2 = this->s.emplace_hint(this->s.begin(), {0, "zero"}); EXPECT_TRUE(result2.second); EXPECT_EQ(result2.first->value, 0); EXPECT_EQ(result2.first->name, "zero"); EXPECT_EQ(this->s.size(), 4); } TEST_F(SortedVectorSetIntTest, ConstIterators) { this->s.insert(1); this->s.insert(2); this->s.insert(3); const sorted_vector_set<int>& const_s = this->s; // Treat as const sorted_vector_set<int>::const_iterator cit = const_s.begin(); EXPECT_EQ(*cit, 1); sorted_vector_set<int>::const_reverse_iterator crit = const_s.rbegin(); EXPECT_EQ(*crit, 3); auto cbegin_it = const_s.cbegin(); auto cend_it = const_s.cend(); EXPECT_EQ(*cbegin_it, 1); EXPECT_NE(cbegin_it, cend_it); EXPECT_EQ(std::distance(cbegin_it, cend_it), 3); auto crbegin_it = const_s.crbegin(); auto crend_it = const_s.crend(); EXPECT_EQ(*crbegin_it, 3); EXPECT_NE(crbegin_it, crend_it); EXPECT_EQ(std::distance(crbegin_it, crend_it), 3); } TEST_F(SortedVectorSetIntTest, RangeBasedForLoop) { this->s.insert(3); this->s.insert(1); this->s.insert(2); int expected = 1; for (int value : this->s) { EXPECT_EQ(value, expected); expected++; } } TEST_F(SortedVectorSetIntTest, CopyConstructor) { this->s.insert(1); this->s.insert(2); sorted_vector_set<int> s2(this->s); // Copy constructor EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); // Ensure the copy is independent s2.insert(3); EXPECT_EQ(this->s.size(), 2); EXPECT_EQ(s2.size(), 3); } TEST_F(SortedVectorSetIntTest, CopyAssignmentOperator) { this->s.insert(1); this->s.insert(2); sorted_vector_set<int> s2; s2 = this->s; // Copy assignment EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); // Ensure the copy is independent s2.insert(3); EXPECT_EQ(this->s.size(), 2); EXPECT_EQ(s2.size(), 3); } TEST_F(SortedVectorSetIntTest, MoveConstructor) { this->s.insert(1); this->s.insert(2); size_t s_capacity = this->s.capacity(); sorted_vector_set<int> s2(std::move(this->s)); // Move constructor EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); EXPECT_EQ(s2.capacity(), s_capacity); // Capacity should be moved // this->s is now in a valid but unspecified state, but often empty // EXPECT_EQ(this->s.size(), 0); // Not guaranteed to be 0. // EXPECT_TRUE(this->s.empty()); } TEST_F(SortedVectorSetIntTest, MoveAssignmentOperator) { this->s.insert(1); this->s.insert(2); size_t s_capacity = this->s.capacity(); sorted_vector_set<int> s2; s2 = std::move(this->s); // Move assignment EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); EXPECT_EQ(s2.capacity(), s_capacity); // this->s is now in a valid but unspecified state. } TEST_F(SortedVectorSetIntTest, VectorAccessorAndIndexing) { this->s.insert(10); this->s.insert(20); this->s.insert(30); const std::vector<int>& underlyingVector = this->s.vector(); auto it = underlyingVector.begin(); EXPECT_EQ(*it++, 10); EXPECT_EQ(*it++, 20); EXPECT_EQ(*it, 30); EXPECT_EQ(this->s.vector().front(), 10); EXPECT_EQ(this->s.vector().back(), 30); } TEST_F(SortedVectorSetStringTest, StringMoveSemantics) { std::string str1 = "hello"; std::string str2 = "world"; this->s.insert(std::move(str1)); EXPECT_EQ(this->s.size(), 1); EXPECT_EQ(*this->s.begin(), "hello"); EXPECT_NE(*this->s.begin(), str1); // String should be moved from. this->s.emplace(std::move(str2)); EXPECT_EQ(this->s.size(), 2); auto it = this->s.begin(); it++; EXPECT_EQ(*it, "world"); EXPECT_NE(*it, str2); } TEST_F(SortedVectorSetStringTest, HeterogeneousComparison) { this->s.insert("apple"); this->s.insert("banana"); this->s.insert("cherry"); EXPECT_TRUE(this->s.contains(std::string_view("apple"))); EXPECT_TRUE(this->s.contains("banana")); EXPECT_FALSE(this->s.contains(std::string_view("grape"))); EXPECT_FALSE(this->s.contains("grapefruit")); EXPECT_EQ(*this->s.find(std::string_view("apple")), "apple"); EXPECT_EQ(*this->s.find("banana"), "banana"); EXPECT_EQ(this->s.find(std::string_view("grape")), this->s.end()); EXPECT_EQ(this->s.find("grapefruit"), this->s.end()); EXPECT_EQ(*this->s.lower_bound(std::string_view("banana")), "banana"); EXPECT_EQ(*this->s.lower_bound("banana"), "banana"); EXPECT_EQ(this->s.lower_bound(std::string_view("grape")), this->s.end()); EXPECT_EQ(this->s.lower_bound("grape"), this->s.end()); EXPECT_EQ(this->s.erase(std::string_view("banana")), 1); EXPECT_EQ(this->s.size(), 2); EXPECT_EQ(this->s.erase("orange"), 0); EXPECT_EQ(this->s.size(), 2); } TEST_F(SortedVectorSetIntTest, Erase) { this->s.insert(1); this->s.insert(2); this->s.insert(3); this->s.insert(4); EXPECT_EQ(this->s.erase(0), 0); EXPECT_EQ(this->s.size(), 4); EXPECT_EQ(this->s.erase(1), 1); EXPECT_EQ(this->s.size(), 3); EXPECT_FALSE(this->s.contains(1)); EXPECT_EQ(this->s.erase(1), 0); EXPECT_EQ(this->s.size(), 3); EXPECT_FALSE(this->s.contains(1)); EXPECT_TRUE(std::ranges::is_sorted(this->s)); EXPECT_EQ(*this->s.erase(this->s.begin() + 1), 4); EXPECT_EQ(this->s.size(), 2); EXPECT_TRUE(std::ranges::is_sorted(this->s)); } } // namespace android Loading
libs/androidfw/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -231,6 +231,7 @@ cc_test { "tests/ResourceTimer_test.cpp", "tests/ResourceUtils_test.cpp", "tests/ResTable_test.cpp", "tests/sorted_vector_set_test.cpp", "tests/Split_test.cpp", "tests/StringPiece_test.cpp", "tests/StringPool_test.cpp", Loading
libs/androidfw/include/androidfw/LoadedArsc.h +2 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ #include "androidfw/Chunk.h" #include "androidfw/Idmap.h" #include "androidfw/ResourceTypes.h" #include "androidfw/sorted_vector_set.h" #include "androidfw/Util.h" namespace android { Loading Loading @@ -238,7 +239,7 @@ class LoadedPackage { // Populates a set of strings representing locales. // If `canonicalize` is set to true, each locale is transformed into its canonical format // before being inserted into the set. This may cause some equivalent locales to de-dupe. using Locales = std::set<std::string, std::less<>>; using Locales = sorted_vector_set<std::string>; void CollectLocales(bool canonicalize, Locales* out_locales) const; // type_idx is TT - 1 from 0xPPTTEEEE. Loading
libs/androidfw/include/androidfw/sorted_vector_set.h 0 → 100644 +204 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <algorithm> #include <concepts> #include <utility> #include <vector> namespace android { // The implementation of std::set that holds all items in a sorted vector - basically std::flat_set // with a little fewer generic features in favor of code complexity. // Given that libc++ decided to not implement it for now, this class can be used until it's // available in the STL. // // This is the most efficient way of storing a rarely modified collection of items - all the // lookups happen faster because of the cache locality, there's much less per-iterm memory overhead, // and in order iteration is the fastest it can be on modern hardware. // // The tradeoff is in the insertion and erasure complexity - this container has to move the whole // trail of elements when modifying one in the middle, turning it into a linear operation instead // of a logarithmic one. // // Also, beware of the iterator and pointer stability - the underlying vector can resize at any // insertion, so insertions invalidate all iterators and pointers, and removals invalidate // everything past the removed element. // // TODO(b/406585404): replace with std::flat_set<> when available. // A way to ensure that the templated lookup functions are only called with the compatible types. template <class T, class U, class Cmp> concept Comparable = requires(const T t, const U u, Cmp c) { { c(t, u) } -> std::convertible_to<bool>; { c(u, t) } -> std::convertible_to<bool>; }; template <class T, class Cmp = std::less<>> class sorted_vector_set : std::vector<T> { using base = std::vector<T>; public: // Set doesn't let you modify its elements via iterators. using iterator = typename base::const_iterator; using reverse_iterator = typename base::const_reverse_iterator; using const_iterator = iterator; using const_reverse_iterator = reverse_iterator; using value_type = typename base::value_type; using reference = const value_type&; using size_type = typename base::size_type; using key_compare = Cmp; using value_compare = Cmp; sorted_vector_set() = default; explicit sorted_vector_set(Cmp cmp) : mCmp(std::move(cmp)) { } sorted_vector_set(size_t reserve_size) { base::reserve(reserve_size); } // Const functions mostly can be passed through. using base::capacity; using base::cbegin; using base::cend; using base::crbegin; using base::crend; using base::empty; using base::size; // These are totally safe as well. using base::clear; using base::erase; using base::pop_back; using base::reserve; using base::shrink_to_fit; iterator begin() const { return cbegin(); } iterator end() const { return cend(); } reverse_iterator rbegin() const { return crbegin(); } reverse_iterator rend() const { return crend(); } key_compare key_comp() const { return mCmp; } value_compare value_comp() const { return mCmp; } // Add the vector interface accessor as well. const base& vector() const { return *this; } // Now, set interface. template <class Key = T> requires Comparable<Key, T, Cmp> bool contains(const Key& k) const { return find(k) != base::cend(); } template <class Key = T> requires Comparable<Key, T, Cmp> const_iterator find(const Key& k) const { auto it = lower_bound(k); if (it == base::cend()) return it; if (mCmp(k, *it)) return base::end(); return it; } template <class Key = T> requires Comparable<Key, T, Cmp> const_iterator lower_bound(const Key& k) const { return std::lower_bound(base::begin(), base::end(), k, mCmp); } std::pair<iterator, bool> insert(const T& t) { return insert(T(t)); } std::pair<iterator, bool> insert(T&& t) { auto it = lower_bound(t); if (it != base::cend() && !mCmp(t, *it)) { return {it, false}; } return {base::insert(it, std::move(t)), true}; } template <class Key = T> requires Comparable<Key, T, Cmp> std::pair<iterator, bool> emplace(Key&& k) { auto it = lower_bound(k); if (it != base::cend() && !mCmp(k, *it)) { return {it, false}; } return {base::emplace(it, std::forward<Key>(k)), true}; } template <class Key = T> requires Comparable<Key, T, Cmp> std::pair<iterator, bool> emplace_hint(iterator hint, Key&& k) { // Check if the hint is in the correct position. if ((hint != base::cend() && mCmp(*hint, k)) || (hint != base::cbegin() && mCmp(k, *(std::prev(hint))))) { // No, discard it. return emplace(std::forward<Key>(k)); } return emplace_impl(hint, std::forward<Key>(k)); } template <class Key = T> requires Comparable<Key, T, Cmp> size_t count(const Key& k) const { return contains(k); } template <class Key = T> requires Comparable<Key, T, Cmp> size_t erase(const Key& k) { const auto it = find(k); if (it == cend()) { return 0; } base::erase(it); return 1; } private: template <class Key> std::pair<iterator, bool> emplace_impl(iterator pos, Key&& k) { if (pos != base::cend() && !mCmp(k, *pos)) { return {pos, false}; } return {base::emplace(pos, std::forward<Key>(k)), true}; } private: [[no_unique_address]] Cmp mCmp; }; } // namespace android
libs/androidfw/tests/sorted_vector_set_test.cpp 0 → 100644 +485 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "androidfw/sorted_vector_set.h" #include <gtest/gtest.h> #include <algorithm> #include <memory> #include <set> #include <utility> #include <vector> namespace android { namespace { template <class SortedVectorContainer> class SortedVectorTestBase : public ::testing::Test { public: void TearDown() override { EXPECT_TRUE(std::ranges::is_sorted(s, s.key_comp())); } protected: SortedVectorContainer s; }; class SortedVectorSetIntTest : public SortedVectorTestBase<sorted_vector_set<int>> {}; class SortedVectorSetIntGreaterTest : public SortedVectorTestBase<sorted_vector_set<int, std::greater<int>>> {}; struct MyStruct { int value; std::string name; bool operator==(const MyStruct& other) const { return value == other.value && name == other.name; } }; struct MyStructComparator { bool operator()(const MyStruct& a, const MyStruct& b) const { return a.value < b.value; // Compare based on value } }; class SortedVectorSetMyStructTest : public SortedVectorTestBase<sorted_vector_set<MyStruct, MyStructComparator>> {}; class SortedVectorSetStringTest : public SortedVectorTestBase<sorted_vector_set<std::string>> {}; } // namespace TEST_F(SortedVectorSetIntTest, DefaultConstructor) { EXPECT_TRUE(this->s.empty()); EXPECT_EQ(this->s.size(), 0); } TEST_F(SortedVectorSetIntTest, SizeConstructor) { sorted_vector_set<int> s2(10); // Reserve space for 10 elements. EXPECT_TRUE(s2.empty()); EXPECT_EQ(s2.size(), 0); EXPECT_GE(s2.capacity(), 10); } TEST_F(SortedVectorSetIntTest, InsertAndFind) { auto result1 = this->s.insert(5); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, 5); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.insert(5); EXPECT_FALSE(result2.second); EXPECT_EQ(*result2.first, 5); EXPECT_EQ(this->s.size(), 1); EXPECT_TRUE(this->s.contains(5)); EXPECT_FALSE(this->s.contains(10)); auto it = this->s.find(5); EXPECT_EQ(*it, 5); auto it2 = this->s.find(10); EXPECT_EQ(it2, this->s.end()); auto it3 = this->s.find(1); EXPECT_EQ(it3, this->s.end()); } TEST_F(SortedVectorSetIntTest, InsertMultipleAndFind) { this->s.insert(30); this->s.insert(10); this->s.insert(20); this->s.insert(50); this->s.insert(40); EXPECT_EQ(this->s.size(), 5); auto it = this->s.begin(); EXPECT_EQ(*it++, 10); EXPECT_EQ(*it++, 20); EXPECT_EQ(*it++, 30); EXPECT_EQ(*it++, 40); EXPECT_EQ(*it, 50); EXPECT_EQ(this->s.find(5), this->s.end()); EXPECT_EQ(this->s.find(15), this->s.end()); EXPECT_EQ(this->s.find(25), this->s.end()); EXPECT_EQ(this->s.find(35), this->s.end()); EXPECT_EQ(this->s.find(45), this->s.end()); EXPECT_EQ(this->s.find(55), this->s.end()); } TEST_F(SortedVectorSetIntTest, Emplace) { auto result1 = this->s.emplace(5); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, 5); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.emplace(5); EXPECT_FALSE(result2.second); EXPECT_EQ(*result2.first, 5); EXPECT_EQ(this->s.size(), 1); } TEST_F(SortedVectorSetIntTest, EmplaceHint) { this->s.insert(1); this->s.insert(3); this->s.insert(6); auto hint = this->s.find(3); auto result1 = this->s.emplace_hint(hint, 4); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, 4); EXPECT_EQ(this->s.size(), 4); auto result2 = this->s.emplace_hint(this->s.begin(), 2); EXPECT_TRUE(result2.second); EXPECT_EQ(*result2.first, 2); EXPECT_EQ(this->s.size(), 5); auto result3 = this->s.emplace_hint(this->s.end(), 10); EXPECT_TRUE(result3.second); EXPECT_EQ(*result3.first, 10); EXPECT_EQ(this->s.size(), 6); auto result4 = this->s.emplace_hint(this->s.find(3), 3); EXPECT_FALSE(result4.second); EXPECT_EQ(*result4.first, 3); EXPECT_EQ(this->s.size(), 6); auto result5 = this->s.emplace_hint(this->s.find(10), 5); EXPECT_TRUE(result5.second); EXPECT_EQ(*result5.first, 5); EXPECT_EQ(this->s.size(), 7); } TEST_F(SortedVectorSetIntTest, EmplaceHintBeginning) { this->s.insert(2); auto hint = this->s.begin(); auto result = this->s.emplace_hint(hint, 0); EXPECT_TRUE(result.second); EXPECT_EQ(*result.first, 0); EXPECT_EQ(this->s.size(), 2); result = this->s.emplace_hint(this->s.end(), 1); EXPECT_TRUE(result.second); EXPECT_EQ(*result.first, 1); EXPECT_EQ(this->s.size(), 3); } TEST_F(SortedVectorSetIntTest, EmplaceHintEnd) { this->s.insert(1); auto hint = this->s.end(); auto result = this->s.emplace_hint(hint, 2); EXPECT_TRUE(result.second); EXPECT_EQ(*result.first, 2); EXPECT_EQ(this->s.size(), 2); } TEST_F(SortedVectorSetIntTest, EmplaceHintExisting) { this->s.insert(1); this->s.insert(2); auto hint = this->s.find(1); auto result = this->s.emplace_hint(hint, 1); EXPECT_FALSE(result.second); EXPECT_EQ(*result.first, 1); EXPECT_EQ(this->s.size(), 2); } TEST_F(SortedVectorSetIntTest, Count) { this->s.insert(5); EXPECT_EQ(this->s.count(5), 1); EXPECT_EQ(this->s.count(10), 0); } TEST_F(SortedVectorSetIntGreaterTest, CustomComparator) { this->s.insert(5); this->s.insert(10); this->s.insert(1); auto it = this->s.begin(); EXPECT_EQ(*it++, 10); EXPECT_EQ(*it++, 5); EXPECT_EQ(*it, 1); } TEST_F(SortedVectorSetMyStructTest, InsertWithCustomComparator) { MyStruct a(1, "one"); MyStruct b(2, "two"); MyStruct c(3, "three"); auto result1 = this->s.insert(a); EXPECT_TRUE(result1.second); EXPECT_EQ(*result1.first, a); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.insert(b); EXPECT_TRUE(result2.second); EXPECT_EQ(*result2.first, b); EXPECT_EQ(this->s.size(), 2); auto result3 = this->s.insert(a); // Duplicate EXPECT_FALSE(result3.second); EXPECT_EQ(*result3.first, a); EXPECT_EQ(this->s.size(), 2); EXPECT_TRUE(this->s.contains(a)); EXPECT_TRUE(this->s.contains(b)); EXPECT_FALSE(this->s.contains(c)); } TEST_F(SortedVectorSetMyStructTest, FindWithCustomComparator) { MyStruct a(1, "one"); MyStruct b(2, "two"); this->s.insert(a); this->s.insert(b); auto it1 = this->s.find(a); EXPECT_EQ(*it1, a); auto it2 = this->s.find(b); EXPECT_EQ(*it2, b); MyStruct c(3, "three"); auto it3 = this->s.find(c); EXPECT_EQ(it3, this->s.end()); } TEST_F(SortedVectorSetMyStructTest, LowerBoundWithCustomComparator) { MyStruct a(1, "one"); MyStruct b(2, "two"); MyStruct c(3, "three"); this->s.insert(a); this->s.insert(c); auto it1 = this->s.lower_bound(b); EXPECT_EQ(*it1, c); // lower_bound of 2 is 3 auto it2 = this->s.lower_bound(a); EXPECT_EQ(*it2, a); } TEST_F(SortedVectorSetMyStructTest, EmplaceWithCustomComparator) { auto result1 = this->s.emplace({1, "one"}); EXPECT_TRUE(result1.second); EXPECT_EQ(result1.first->value, 1); EXPECT_EQ(result1.first->name, "one"); EXPECT_EQ(this->s.size(), 1); auto result2 = this->s.emplace({1, "another"}); // Duplicate value EXPECT_FALSE(result2.second); EXPECT_EQ(result2.first->value, 1); EXPECT_EQ(result2.first->name, "one"); // Should not change EXPECT_EQ(this->s.size(), 1); } TEST_F(SortedVectorSetMyStructTest, EmplaceHintWithCustomComparator) { this->s.emplace({1, "one"}); this->s.emplace({3, "three"}); auto hint = this->s.find(MyStruct(3, "three")); auto result1 = this->s.emplace_hint(hint, {2, "two"}); EXPECT_TRUE(result1.second); EXPECT_EQ(result1.first->value, 2); EXPECT_EQ(result1.first->name, "two"); EXPECT_EQ(this->s.size(), 3); auto result2 = this->s.emplace_hint(this->s.begin(), {0, "zero"}); EXPECT_TRUE(result2.second); EXPECT_EQ(result2.first->value, 0); EXPECT_EQ(result2.first->name, "zero"); EXPECT_EQ(this->s.size(), 4); } TEST_F(SortedVectorSetIntTest, ConstIterators) { this->s.insert(1); this->s.insert(2); this->s.insert(3); const sorted_vector_set<int>& const_s = this->s; // Treat as const sorted_vector_set<int>::const_iterator cit = const_s.begin(); EXPECT_EQ(*cit, 1); sorted_vector_set<int>::const_reverse_iterator crit = const_s.rbegin(); EXPECT_EQ(*crit, 3); auto cbegin_it = const_s.cbegin(); auto cend_it = const_s.cend(); EXPECT_EQ(*cbegin_it, 1); EXPECT_NE(cbegin_it, cend_it); EXPECT_EQ(std::distance(cbegin_it, cend_it), 3); auto crbegin_it = const_s.crbegin(); auto crend_it = const_s.crend(); EXPECT_EQ(*crbegin_it, 3); EXPECT_NE(crbegin_it, crend_it); EXPECT_EQ(std::distance(crbegin_it, crend_it), 3); } TEST_F(SortedVectorSetIntTest, RangeBasedForLoop) { this->s.insert(3); this->s.insert(1); this->s.insert(2); int expected = 1; for (int value : this->s) { EXPECT_EQ(value, expected); expected++; } } TEST_F(SortedVectorSetIntTest, CopyConstructor) { this->s.insert(1); this->s.insert(2); sorted_vector_set<int> s2(this->s); // Copy constructor EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); // Ensure the copy is independent s2.insert(3); EXPECT_EQ(this->s.size(), 2); EXPECT_EQ(s2.size(), 3); } TEST_F(SortedVectorSetIntTest, CopyAssignmentOperator) { this->s.insert(1); this->s.insert(2); sorted_vector_set<int> s2; s2 = this->s; // Copy assignment EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); // Ensure the copy is independent s2.insert(3); EXPECT_EQ(this->s.size(), 2); EXPECT_EQ(s2.size(), 3); } TEST_F(SortedVectorSetIntTest, MoveConstructor) { this->s.insert(1); this->s.insert(2); size_t s_capacity = this->s.capacity(); sorted_vector_set<int> s2(std::move(this->s)); // Move constructor EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); EXPECT_EQ(s2.capacity(), s_capacity); // Capacity should be moved // this->s is now in a valid but unspecified state, but often empty // EXPECT_EQ(this->s.size(), 0); // Not guaranteed to be 0. // EXPECT_TRUE(this->s.empty()); } TEST_F(SortedVectorSetIntTest, MoveAssignmentOperator) { this->s.insert(1); this->s.insert(2); size_t s_capacity = this->s.capacity(); sorted_vector_set<int> s2; s2 = std::move(this->s); // Move assignment EXPECT_EQ(s2.size(), 2); auto it = s2.begin(); EXPECT_EQ(*it++, 1); EXPECT_EQ(*it, 2); EXPECT_EQ(s2.capacity(), s_capacity); // this->s is now in a valid but unspecified state. } TEST_F(SortedVectorSetIntTest, VectorAccessorAndIndexing) { this->s.insert(10); this->s.insert(20); this->s.insert(30); const std::vector<int>& underlyingVector = this->s.vector(); auto it = underlyingVector.begin(); EXPECT_EQ(*it++, 10); EXPECT_EQ(*it++, 20); EXPECT_EQ(*it, 30); EXPECT_EQ(this->s.vector().front(), 10); EXPECT_EQ(this->s.vector().back(), 30); } TEST_F(SortedVectorSetStringTest, StringMoveSemantics) { std::string str1 = "hello"; std::string str2 = "world"; this->s.insert(std::move(str1)); EXPECT_EQ(this->s.size(), 1); EXPECT_EQ(*this->s.begin(), "hello"); EXPECT_NE(*this->s.begin(), str1); // String should be moved from. this->s.emplace(std::move(str2)); EXPECT_EQ(this->s.size(), 2); auto it = this->s.begin(); it++; EXPECT_EQ(*it, "world"); EXPECT_NE(*it, str2); } TEST_F(SortedVectorSetStringTest, HeterogeneousComparison) { this->s.insert("apple"); this->s.insert("banana"); this->s.insert("cherry"); EXPECT_TRUE(this->s.contains(std::string_view("apple"))); EXPECT_TRUE(this->s.contains("banana")); EXPECT_FALSE(this->s.contains(std::string_view("grape"))); EXPECT_FALSE(this->s.contains("grapefruit")); EXPECT_EQ(*this->s.find(std::string_view("apple")), "apple"); EXPECT_EQ(*this->s.find("banana"), "banana"); EXPECT_EQ(this->s.find(std::string_view("grape")), this->s.end()); EXPECT_EQ(this->s.find("grapefruit"), this->s.end()); EXPECT_EQ(*this->s.lower_bound(std::string_view("banana")), "banana"); EXPECT_EQ(*this->s.lower_bound("banana"), "banana"); EXPECT_EQ(this->s.lower_bound(std::string_view("grape")), this->s.end()); EXPECT_EQ(this->s.lower_bound("grape"), this->s.end()); EXPECT_EQ(this->s.erase(std::string_view("banana")), 1); EXPECT_EQ(this->s.size(), 2); EXPECT_EQ(this->s.erase("orange"), 0); EXPECT_EQ(this->s.size(), 2); } TEST_F(SortedVectorSetIntTest, Erase) { this->s.insert(1); this->s.insert(2); this->s.insert(3); this->s.insert(4); EXPECT_EQ(this->s.erase(0), 0); EXPECT_EQ(this->s.size(), 4); EXPECT_EQ(this->s.erase(1), 1); EXPECT_EQ(this->s.size(), 3); EXPECT_FALSE(this->s.contains(1)); EXPECT_EQ(this->s.erase(1), 0); EXPECT_EQ(this->s.size(), 3); EXPECT_FALSE(this->s.contains(1)); EXPECT_TRUE(std::ranges::is_sorted(this->s)); EXPECT_EQ(*this->s.erase(this->s.begin() + 1), 4); EXPECT_EQ(this->s.size(), 2); EXPECT_TRUE(std::ranges::is_sorted(this->s)); } } // namespace android