From ac3052223465ce654457fec17b34f27de1706e57 Mon Sep 17 00:00:00 2001 From: Gil Date: Wed, 31 Jan 2018 14:28:20 -0800 Subject: Start on ArraySortedMap in C++ (#721) * Implement ArraySortedMap.remove * Implement ArraySortedMap.insert * Ensure ArraySortedMap.insert avoids copying on duplicates * Port more ArraySortedMapTests --- Firestore/core/CMakeLists.txt | 2 + .../firebase/firestore/immutable/CMakeLists.txt | 21 ++ .../firestore/immutable/array_sorted_map.cc | 30 ++ .../firestore/immutable/array_sorted_map.h | 318 ++++++++++++++++++++ .../src/firebase/firestore/immutable/map_entry.h | 62 ++++ .../firebase/firestore/immutable/CMakeLists.txt | 22 ++ .../firestore/immutable/array_sorted_map_test.cc | 328 +++++++++++++++++++++ 7 files changed, 783 insertions(+) create mode 100644 Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt create mode 100644 Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc create mode 100644 Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h create mode 100644 Firestore/core/src/firebase/firestore/immutable/map_entry.h create mode 100644 Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt create mode 100644 Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc (limited to 'Firestore/core') diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index f143a71..2fc88c6 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -14,12 +14,14 @@ add_subdirectory(src/firebase/firestore) add_subdirectory(src/firebase/firestore/core) +add_subdirectory(src/firebase/firestore/immutable) add_subdirectory(src/firebase/firestore/model) add_subdirectory(src/firebase/firestore/remote) add_subdirectory(src/firebase/firestore/util) add_subdirectory(test/firebase/firestore) add_subdirectory(test/firebase/firestore/core) +add_subdirectory(test/firebase/firestore/immutable) add_subdirectory(test/firebase/firestore/model) add_subdirectory(test/firebase/firestore/remote) add_subdirectory(test/firebase/firestore/util) diff --git a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt new file mode 100644 index 0000000..e8a95cd --- /dev/null +++ b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2018 Google +# +# 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. + +cc_library( + firebase_firestore_immutable + SOURCES + array_sorted_map.cc + array_sorted_map.h + map_entry.h +) diff --git a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc new file mode 100644 index 0000000..48e7553 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.cc @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Google + * + * 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 "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h" + +namespace firebase { +namespace firestore { +namespace immutable { +namespace impl { + +// Define external storage for constants: +constexpr ArraySortedMapBase::size_type ArraySortedMapBase::kFixedSize; + +} // namespace impl +} // namespace immutable +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h new file mode 100644 index 0000000..d0210a8 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h @@ -0,0 +1,318 @@ +/* + * Copyright 2018 Google + * + * 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_ + +#include +#include +#include +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/immutable/map_entry.h" + +namespace firebase { +namespace firestore { +namespace immutable { + +namespace impl { + +/** + * A base class for implementing ArraySortedMap, containing types and constants + * that don't depend upon the template parameters to the main class. + * + * Note that this exists as a base class rather than as just a namespace in + * order to make it possible for users of ArraySortedMap to avoid needing to + * declare storage for each instantiation of the template. + */ +class ArraySortedMapBase { + public: + /** + * The type of size() methods on immutable collections. Note that this is not + * size_t specifically to save space in the TreeSortedMap implementation. + */ + using size_type = uint32_t; + + /** + * The maximum size of an ArraySortedMap. + * + * This is the size threshold where we use a tree backed sorted map instead of + * an array backed sorted map. This is a more or less arbitrary chosen value, + * that was chosen to be large enough to fit most of object kind of Firebase + * data, but small enough to not notice degradation in performance for + * inserting and lookups. Feel free to empirically determine this constant, + * but don't expect much gain in real world performance. + */ + // TODO(wilhuff): actually use this for switching implementations. + static constexpr size_type kFixedSize = 25; +}; + +/** + * A bounded-size array that allocates its contents directly in itself. This + * saves a heap allocation when compared with std::vector (though std::vector + * can resize itself while FixedArray cannot). + * + * Unlike std::array, FixedArray keeps track of its size and grows up to the + * fixed_size limit. Inserting more elements than fixed_size will trigger an + * assertion failure. + * + * ArraySortedMap does not actually contain its array: it contains a shared_ptr + * to a FixedArray. + * + * @tparam T The type of an element in the array. + * @tparam fixed_size the fixed size to use in creating the FixedArray. + */ +template +class FixedArray { + public: + using size_type = ArraySortedMapBase::size_type; + using array_type = std::array; + using iterator = typename array_type::iterator; + using const_iterator = typename array_type::const_iterator; + + FixedArray() { + } + + template + FixedArray(SourceIterator src_begin, SourceIterator src_end) { + append(src_begin, src_end); + } + + /** + * Appends to this array, copying from the given src_begin up to but not + * including the src_end. + */ + template + void append(SourceIterator src_begin, SourceIterator src_end) { + size_type appending = static_cast(src_end - src_begin); + size_type new_size = size_ + appending; + assert(new_size <= fixed_size); + + std::copy(src_begin, src_end, end()); + size_ = new_size; + } + + /** + * Appends a single value to the array. + */ + void append(T&& value) { + size_type new_size = size_ + 1; + assert(new_size <= fixed_size); + + *end() = std::move(value); + size_ = new_size; + } + + const_iterator begin() const { + return contents_.begin(); + } + + const_iterator end() const { + return begin() + size_; + } + + size_type size() const { + return size_; + } + + private: + iterator begin() { + return contents_.begin(); + } + + iterator end() { + return begin() + size_; + } + + array_type contents_; + size_type size_ = 0; +}; + +} // namespace impl + +/** + * ArraySortedMap is a value type containing a map. It is immutable, but has + * methods to efficiently create new maps that are mutations of it. + */ +template > +class ArraySortedMap : public impl::ArraySortedMapBase { + public: + using key_comparator_type = KeyComparator; + + /** + * The type of the entries stored in the map. + */ + using value_type = std::pair; + + /** + * The type of the fixed-size array containing entries of value_type. + */ + using array_type = impl::FixedArray; + using const_iterator = typename array_type::const_iterator; + + using array_pointer = std::shared_ptr; + + /** + * Creates an empty ArraySortedMap. + */ + explicit ArraySortedMap(const C& comparator = C()) + : array_(EmptyArray()), key_comparator_(comparator) { + } + + /** + * Creates an ArraySortedMap containing the given entries. + */ + ArraySortedMap(std::initializer_list entries, + const C& comparator = C()) + : array_(std::make_shared(entries.begin(), entries.end())), + key_comparator_(comparator) { + } + + /** + * Creates a new map identical to this one, but with a key-value pair added or + * updated. + * + * @param key The key to insert/update. + * @param value The value to associate with the key. + * @return A new dictionary with the added/updated value. + */ + ArraySortedMap insert(const K& key, const V& value) const { + const_iterator current_end = end(); + const_iterator pos = LowerBound(key); + bool replacing_entry = false; + + if (pos != current_end) { + // LowerBound found an entry where pos->first >= pair.first. Reversing the + // argument order here tests pair.first < pos->first. + replacing_entry = !key_comparator_(key, *pos); + if (replacing_entry && value == pos->second) { + return *this; + } + } + + // Copy the segment before the found position. If not found, this is + // everything. + auto copy = std::make_shared(begin(), pos); + + // Copy the value to be inserted. + copy->append(value_type(key, value)); + + if (replacing_entry) { + // Skip the thing at pos because it compares the same as the pair above. + copy->append(pos + 1, current_end); + } else { + copy->append(pos, current_end); + } + return wrap(copy); + } + + /** + * Creates a new map identical to this one, but with a key removed from it. + * + * @param key The key to remove. + * @return A new dictionary without that value. + */ + ArraySortedMap erase(const K& key) const { + const_iterator current_end = end(); + const_iterator pos = find(key); + if (pos == current_end) { + return *this; + } else if (size() <= 1) { + // If the key was found and it's the last entry, removing it would make + // the result empty. + return wrap(EmptyArray()); + } else { + auto copy = std::make_shared(begin(), pos); + copy->append(pos + 1, current_end); + return wrap(copy); + } + } + + /** + * Finds a value in the map. + * + * @param key The key to look up. + * @return An iterator pointing to the entry containing the key, or end() if + * not found. + */ + const_iterator find(const K& key) const { + const_iterator not_found = end(); + const_iterator lower_bound = LowerBound(key); + if (lower_bound != not_found && !key_comparator_(key, *lower_bound)) { + return lower_bound; + } else { + return not_found; + } + } + + // TODO(wilhuff): indexof + + /** Returns true if the map contains no elements. */ + bool empty() const { + return size() == 0; + } + + /** Returns the number of items in this map. */ + size_type size() const { + return array_->size(); + } + + /** + * Returns an iterator pointing to the first entry in the map. If there are + * no entries in the map, begin() == end(). + */ + const_iterator begin() const { + return array_->begin(); + } + + /** + * Returns an iterator pointing past the last entry in the map. + */ + const_iterator end() const { + return array_->end(); + } + + private: + static array_pointer EmptyArray() { + static const array_pointer kEmptyArray = + std::make_shared(); + return kEmptyArray; + } + + ArraySortedMap(const array_pointer& array, + const key_comparator_type& key_comparator) noexcept + : array_(array), key_comparator_(key_comparator) { + } + + ArraySortedMap wrap(const array_pointer& array) const noexcept { + return ArraySortedMap(array, key_comparator_); + } + + const_iterator LowerBound(const K& key) const { + return std::lower_bound(begin(), end(), key, key_comparator_); + } + + array_pointer array_; + key_comparator_type key_comparator_; +}; + +} // namespace immutable +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_ARRAY_SORTED_MAP_H_ diff --git a/Firestore/core/src/firebase/firestore/immutable/map_entry.h b/Firestore/core/src/firebase/firestore/immutable/map_entry.h new file mode 100644 index 0000000..2130b5b --- /dev/null +++ b/Firestore/core/src/firebase/firestore/immutable/map_entry.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google + * + * 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_ + +#include +#include + +namespace firebase { +namespace firestore { +namespace immutable { + +/** + * Compares two keys out of a map entry. + * + * @tparam K The type of the first value in the pair. + * @tparam V The type of the second value in the pair. + * @tparam C The comparator for use for values of type K + */ +template > +struct KeyComparator { + typedef std::pair pair_type; + + explicit KeyComparator(const C& comparator = C()) + : key_comparator_(comparator) { + } + + bool operator()(const K& lhs, const pair_type& rhs) const noexcept { + return key_comparator_(lhs, rhs.first); + } + + bool operator()(const pair_type& lhs, const K& rhs) const noexcept { + return key_comparator_(lhs.first, rhs); + } + + bool operator()(const pair_type& lhs, const pair_type& rhs) const noexcept { + return key_comparator_(lhs.first, rhs.first); + } + + private: + C key_comparator_; +}; + +} // namespace immutable +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_MAP_ENTRY_H_ diff --git a/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt new file mode 100644 index 0000000..e7b0b6e --- /dev/null +++ b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2018 Google +# +# 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. + +cc_test( + firebase_firestore_immutable_test + SOURCES + array_sorted_map_test.cc + DEPENDS + firebase_firestore_immutable + firebase_firestore_util +) diff --git a/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc new file mode 100644 index 0000000..4dc6a4b --- /dev/null +++ b/Firestore/core/test/firebase/firestore/immutable/array_sorted_map_test.cc @@ -0,0 +1,328 @@ +/* + * Copyright 2018 Google + * + * 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 "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h" + +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/secure_random.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace immutable { + +typedef ArraySortedMap IntMap; +constexpr IntMap::size_type kFixedSize = IntMap::kFixedSize; + +template +testing::AssertionResult NotFound(const ArraySortedMap& set, + const K& key) { + auto found = set.find(key); + if (found == set.end()) { + return testing::AssertionSuccess(); + } else { + return testing::AssertionFailure() + << "Should not have found (" << found->first << ", " << found->second + << ") @ " << found; + } +} + +template +testing::AssertionResult Found(const ArraySortedMap& map, + const K& key, + const V& expected) { + auto found = map.find(key); + if (found == map.end()) { + return testing::AssertionFailure() << "Did not find key " << key; + } + if (found->second == expected) { + return testing::AssertionSuccess(); + } else { + return testing::AssertionFailure() << "Found entry was (" << found->first + << ", " << found->second << ")"; + } +} + +/** + * Creates a vector containing a sequence of integers from the given starting + * element up to, but not including, the given end element, with values + * incremented by the given step. + * + * If step is negative the sequence is in descending order (but still starting + * at start and ending before end). + */ +std::vector Sequence(int start, int end, int step = 1) { + std::vector result; + if (step > 0) { + for (int i = start; i < end; i += step) { + result.push_back(i); + } + } else { + for (int i = start; i > end; i += step) { + result.push_back(i); + } + } + return result; +} + +/** + * Creates a vector containing a sequence of integers with the given number of + * elements, from zero up to, but not including the given value. + */ +std::vector Sequence(int num_elements) { + return Sequence(0, num_elements); +} + +/** + * Creates a copy of the given vector with contents shuffled randomly. + */ +std::vector Shuffled(const std::vector& values) { + std::vector result(values); + util::SecureRandom rng; + std::shuffle(result.begin(), result.end(), rng); + return result; +} + +/** + * Creates a copy of the given vector with contents sorted. + */ +std::vector Sorted(const std::vector& values) { + std::vector result(values); + std::sort(result.begin(), result.end()); + return result; +} + +/** + * Creates a vector of pairs where each pair has the same first and second + * corresponding to an element in the given vector. + */ +std::vector> Pairs(const std::vector& values) { + std::vector> result; + for (auto&& value : values) { + result.emplace_back(value, value); + } + return result; +} + +/** + * Creates an ArraySortedMap by inserting a pair for each value in the vector. + * Each pair will have the same key and value. + */ +IntMap ToMap(const std::vector& values) { + IntMap result; + for (auto&& value : values) { + result = result.insert(value, value); + } + return result; +} + +/** + * Appends the contents of the given container to a new vector. + */ +template +std::vector Append(const Container& container) { + std::vector result; + result.insert(result.begin(), container.begin(), container.end()); + return result; +} + +// TODO(wilhuff): ReverseTraversal + +#define ASSERT_SEQ_EQ(x, y) ASSERT_EQ((x), Append(y)); +#define EXPECT_SEQ_EQ(x, y) EXPECT_EQ((x), Append(y)); + +TEST(ArraySortedMap, SearchForSpecificKey) { + IntMap map{{1, 3}, {2, 4}}; + + ASSERT_TRUE(Found(map, 1, 3)); + ASSERT_TRUE(Found(map, 2, 4)); + ASSERT_TRUE(NotFound(map, 3)); +} + +TEST(ArraySortedMap, RemoveKeyValuePair) { + IntMap map{{1, 3}, {2, 4}}; + + IntMap new_map = map.erase(1); + ASSERT_TRUE(Found(new_map, 2, 4)); + ASSERT_TRUE(NotFound(new_map, 1)); + + // Make sure the original one is not mutated + ASSERT_TRUE(Found(map, 1, 3)); + ASSERT_TRUE(Found(map, 2, 4)); +} + +TEST(ArraySortedMap, MoreRemovals) { + IntMap map = IntMap() + .insert(1, 1) + .insert(50, 50) + .insert(3, 3) + .insert(4, 4) + .insert(7, 7) + .insert(9, 9) + .insert(1, 20) + .insert(18, 18) + .insert(3, 2) + .insert(4, 71) + .insert(7, 42) + .insert(9, 88); + + ASSERT_TRUE(Found(map, 7, 42)); + ASSERT_TRUE(Found(map, 3, 2)); + ASSERT_TRUE(Found(map, 1, 20)); + + IntMap s1 = map.erase(7); + IntMap s2 = map.erase(3); + IntMap s3 = map.erase(1); + + ASSERT_TRUE(NotFound(s1, 7)); + ASSERT_TRUE(Found(s1, 3, 2)); + ASSERT_TRUE(Found(s1, 1, 20)); + + ASSERT_TRUE(Found(s2, 7, 42)); + ASSERT_TRUE(NotFound(s2, 3)); + ASSERT_TRUE(Found(s2, 1, 20)); + + ASSERT_TRUE(Found(s3, 7, 42)); + ASSERT_TRUE(Found(s3, 3, 2)); + ASSERT_TRUE(NotFound(s3, 1)); +} + +TEST(ArraySortedMap, RemovesMiddle) { + IntMap map{{1, 1}, {2, 2}, {3, 3}}; + ASSERT_TRUE(Found(map, 1, 1)); + ASSERT_TRUE(Found(map, 2, 2)); + ASSERT_TRUE(Found(map, 3, 3)); + + IntMap s1 = map.erase(2); + ASSERT_TRUE(Found(s1, 1, 1)); + ASSERT_TRUE(NotFound(s1, 2)); + ASSERT_TRUE(Found(s1, 3, 3)); +} + +TEST(ArraySortedMap, Increasing) { + auto total = static_cast(kFixedSize); + IntMap map; + + for (int i = 0; i < total; i++) { + map = map.insert(i, i); + } + ASSERT_EQ(kFixedSize, map.size()); + + for (int i = 0; i < total; i++) { + map = map.erase(i); + } + ASSERT_EQ(0u, map.size()); +} + +TEST(ArraySortedMap, Override) { + IntMap map = IntMap().insert(10, 10).insert(10, 8); + + ASSERT_TRUE(Found(map, 10, 8)); + ASSERT_FALSE(Found(map, 10, 10)); +} + +TEST(ArraySortedMap, ChecksSize) { + std::vector to_insert = Sequence(kFixedSize); + IntMap map = ToMap(to_insert); + + // Replacing an existing entry should not hit increase size + map = map.insert(5, 10); + + int next = kFixedSize; + ASSERT_DEATH_IF_SUPPORTED(map.insert(next, next), "new_size <= fixed_size"); +} + +TEST(ArraySortedMap, Empty) { + IntMap map = IntMap().insert(10, 10).erase(10); + EXPECT_TRUE(map.empty()); + EXPECT_EQ(0u, map.size()); + EXPECT_TRUE(NotFound(map, 1)); + EXPECT_TRUE(NotFound(map, 10)); +} + +TEST(ArraySortedMap, EmptyGet) { + IntMap map; + EXPECT_TRUE(NotFound(map, 10)); +} + +TEST(ArraySortedMap, EmptySize) { + IntMap map; + EXPECT_TRUE(map.empty()); + EXPECT_EQ(0u, map.size()); +} + +TEST(ArraySortedMap, EmptyRemoval) { + IntMap map; + IntMap new_map = map.erase(1); + EXPECT_TRUE(new_map.empty()); + EXPECT_EQ(0u, new_map.size()); + EXPECT_TRUE(NotFound(new_map, 1)); +} + +TEST(ArraySortedMap, InsertionAndRemovalOfMaxItems) { + auto expected_size = kFixedSize; + int n = static_cast(expected_size); + std::vector to_insert = Shuffled(Sequence(n)); + std::vector to_remove = Shuffled(to_insert); + + // Add them to the map + IntMap map = ToMap(to_insert); + ASSERT_EQ(expected_size, map.size()) + << "Check if all N objects are in the map"; + + // check the order is correct + ASSERT_SEQ_EQ(Pairs(Sorted(to_insert)), map); + + for (int i : to_remove) { + map = map.erase(i); + } + ASSERT_EQ(0u, map.size()) << "Check we removed all of the items"; +} + +TEST(ArraySortedMap, BalanceProblem) { + std::vector to_insert{1, 7, 8, 5, 2, 6, 4, 0, 3}; + + IntMap map = ToMap(to_insert); + ASSERT_SEQ_EQ(Pairs(Sorted(to_insert)), map); +} + +// TODO(wilhuff): PredecessorKey + +// TODO(wilhuff): Iterators + +// TODO(wilhuff): IndexOf + +TEST(ArraySortedMap, AvoidsCopying) { + IntMap map = IntMap().insert(10, 20); + auto found = map.find(10); + ASSERT_NE(found, map.end()); + EXPECT_EQ(20, found->second); + + // Verify that inserting something with equal keys and values just returns + // the same underlying array. + IntMap duped = map.insert(10, 20); + auto duped_found = duped.find(10); + + // If everything worked correctly, the backing array should not have been + // copied and the pointer to the entry with 10 as key should be the same. + EXPECT_EQ(found, duped_found); +} + +} // namespace immutable +} // namespace firestore +} // namespace firebase -- cgit v1.2.3