Commit 80c05933 authored by Zino Benaissa's avatar Zino Benaissa Committed by Facebook GitHub Bot

heap_vector_map, heap_vector_set

Summary:
Define `heap_vector_map` and `heap_vector_set`. These feature a fast lookup (find operation) which does not rely on `std::lower_bound` or similar to find keys. Map elements are not laid out in sorted order. Instead, they are laid out in heap order, which is also known as Eytzinger order. A heap order layout is optimal to maximize memory and cache locality of lookup operations.

A heap vector set containing the elements 0-7 lays out the elements as:

                                     elements  in sorted order
  heap_container[0]  =  4 <-- middle
  heap_container[1]  =  2
  heap_container[2]  =  6
  heap_container[3]  =  1
  heap_container[4]  =  3
  heap_container[5]  =  5
  heap_container[6]  =  7 <-- max
  heap_container[7]  =  0 <-- min

`heap_vector_map` (referred below as HM) is almost a drop-in replacements for `sorted_vector_map` (referred as SM). All SM APIs are supported, and are semantically equal, with very few exceptions.

Although HM works correctly for any key value SM supports, the speedup of find operation (random key search) is typically limited to maps with small key types with comparison operations subject to inlining. Measurements suggest up to 2x speedup of find operation for HM compared to SM.

The key tradeoff is that HM has slower insertion and removal operations and slower iteration. Prefer HM if updates and traversals operations are rare, and lookups are the dominant operation. Of course, SM itself has slow insertion and removal operations so, if these operations are sufficiently common, another map type entirely would be preferable.

Key similarities:

1) They have the same in-situ sizes: `sizeof(HM) == sizeof(SM)`.
2) Insertions and removals. As expected, these operations are slower for HM. Both SM and HM need to move elements while preserving sorted or heap order but the SM operation can be much simpler.
3) A fast construction of HM from a sorted vector or from a SM.
4) An iterator that follows sorted order compatible with SM. This is complex, though. When sorted iteration order is not required, it is faster to use the enclosed container's iteration order.

Key differences:

1) iterate() is a new API that provides the iterator range of the container vector. iterate() enables the fastest traversal of the heap container elements. For example,
          for (auto& element : HS.iterator()) { // traversed as laid out in memory
              std::cout << e << ", ";
           }
for above examples, the loop prints:  4, 2, 6, 1, 3, 5, 7, 0,
           for (auto& element : HS) { // key-sorted
              std::cout << e << ", ";
           }
for above examples, the loop prints:   0, 1, 2, 3, 4, 5, 6, 7,

2) data() is purposely not provided because it does not point to the first elements. If the start address is needed, use instead HM.iterate().data()

Reviewed By: Gownta

Differential Revision: D32128733

fbshipit-source-id: 1df7372720b969ee7a84004ded101db132e0c224
parent c5d1dc7b
......@@ -567,6 +567,7 @@ if (BUILD_TESTS)
TEST f14_fwd_test SOURCES F14FwdTest.cpp
TEST f14_map_test SOURCES F14MapTest.cpp
TEST f14_set_test WINDOWS_DISABLED SOURCES F14SetTest.cpp
TEST heap_vector_types_test SOURCES heap_vector_types_test.cpp
TEST foreach_test SOURCES ForeachTest.cpp
TEST merge_test SOURCES MergeTest.cpp
TEST sparse_byte_set_test SOURCES SparseByteSetTest.cpp
......
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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.
*/
/*
* This header defines two new containers, heap_vector_set and heap_vector_map
* classes. These containers are designed to be a drop-in replacement of
* sorted_vector_set and sorted_vector_map. Similarly to sorted_vector_map/set,
* heap_vector_map/set models AssociativeContainers. Below, we list important
* differences from std::set and std::map (also documented in
* folly/sorted_vector_types.h):
*
* - insert() and erase() invalidate iterators and references.
* - erase(iterator) returns an iterator pointing to the next valid element.
* - insert() and erase() are O(N)
* - our iterators model RandomAccessIterator
* - heap_vector_map::value_type is pair<K,V>, not pair<const K,V>.
* (This is basically because we want to store the value_type in
* std::vector<>, which requires it to be Assignable.)
* - insert() single key variants, emplace(), and emplace_hint() only provide
* the strong exception guarantee (unchanged when exception is thrown) when
* std::is_nothrow_move_constructible<value_type>::value is true.
*
* heap_vector_map/set have exactly the same size as sorted_vector_map/set.
* These containers utilizes a vector container (e.g. std::vector) to store the
* values. Heap containers (similarly to sorted containers) have no additional
* memory overhead. They lay out the data in an optimal way w.r.t locality (see
* https://algorithmica.org/en/eytzinger), called eytzinger or heap order. For
* example in a sorted_vector_set, the underlying vector contains:
* index 0 1 2 3 4 5 6 7 8 9
* vector[index] 0, 10, 20, 30, 40, 50, 60, 70, 80, 90
* while in a heap_vector_set, the underlying vector contains:
* index 0 1 2 3 4 5 6 7 8 9
* vector[index] 60, 30, 80, 10, 50, 70, 90, 0, 20, 40
* Lookup elements in sorted vector containers relies on binary search,
* std::lower_bound. While in heap containers, lookup operation has two
* benefits:
*
* 1. Cache locality, the container is traversed sequentially instead of binary
* search that jumps around the sorted vector.
* 2. The branches in a binary search are mispredicted resulting in hardware
* penalty while using heap lookup search the branch can be avoided by using
* cmov instruction. We observerd look up operations are up to 2X faster than
* sorted_vector_map.
*
* However, Insertion/deletion operations are much slower. If insertions and
* deletions are rare operations for your use case then heap containers might
* be the right choice for you. Also, to minimize impact of insertions while
* creating heap containers, we recommend not to insert element by element
* instead first collect elements in a vector, then construct the heap map from
* it.
*
* Another substantial trade off, inorder traversal of heap container elements
* is slower than sorted vector containers. This is expected as heap map needs
* to jump around to access map elements in order. A remedy is to use underlying
* vector iterators. This works only when the order is irrelevant. For example,
* using heap container iterator:
* for (auto& e: HeapSet)
* std::cout << e << ", ";
* Prints: 0, 10, 20, 30, 40, 50, 60, 70, 80, 90
* and using underlying vector container iterator:
* for (auto& e : HeapSet.iterate())
* std::cout << e << ", ";
* Prints: 60, 30, 80, 10, 50, 70, 90, 0, 20, 40
* The latter loop is the fastest traversal.
*
* Finally The main benefit of heap containers is a compact representation
* that achieves fast random lookup. Use this map when lookup is the
* dominant operation and at the same time saving memory is important.
*/
#pragma once
#include <algorithm>
#include <cassert>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
#include <folly/Range.h>
#include <folly/ScopeGuard.h>
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/lang/Exception.h>
#include <folly/memory/MemoryResource.h>
#include <folly/portability/Builtins.h>
namespace folly {
namespace detail {
namespace heap_vector_detail {
/*
* Heap Containers Helper Functions
* ---------------------------------
* Terminology:
* - offset means the index at which element is stored in the container vector,
* following the heap order.
* - index means the element rank following the container compare order.
*
* Introduction:
* Heap order can be constructed from a sorted input vector using below
* naive algorithm.
*
* heapify(input, output, index = 0, offset = 1) {
* if (offset <= size) {
* index = heapify(input, output, i, 2 * offset);
* output[offset - 1] = input[index++];
* index = heapify(input, output, i, 2 * offset + 1);
* }
* return index;
* }
*
* Helper functions below implement efficient algorithms to:
* - Find offsets of the smallest/greatest elements.
* - Given an offset, calculate the offset where next/previous element is
* stored if it exists.
* - Given a start and an end offsets, calculate distance between their
* corresponding indexes.
* - Fast inplace heapification.
* - Insert a new element while preserving heap order.
* - Delete an element and preserve heap order.
* - find lower/upper bound of a key following container compare order.
*/
// Returns the offset of the smallest element in the heap container.
// The samllest element is stored at:
// vector[2^n - 1] where 2^n <= size < 2^(n+1).
// firstOffset returns 2^n - 1 if size > 0, 0 otherwise
template <typename size_type>
size_type firstOffset(size_type size) {
if (size) {
return (1
<< (CHAR_BIT * sizeof(unsigned long) -
__builtin_clzl((unsigned long)size) - 1)) -
1;
} else {
return 0;
}
}
// Returns the offset of greatest element in heap container.
// the greatest element is storted at:
// vector[2^n - 2] if 2^n - 1 <= size < 2^(n+1)
// lastOffset returns 2^n - 2 if size > 0, 0 otherwise
template <typename size_type>
size_type lastOffset(size_type size) {
if (size) {
return ((size & (size + 1)) == 0 ? size : firstOffset(size)) - 1;
}
return 0;
}
// Returns the offset of the next element. It is calculated based on the
// size of the map.
// To simplify implementation, offset must be 1-based
// return value is also 1-based.
template <typename size_type>
size_type next(size_type offset, size_type size) {
auto next = (2 * offset);
if (next >= size) {
return offset >> __builtin_ffsl((unsigned long)~offset);
} else {
next += 1;
for (auto n = 2 * next; n <= size; n *= 2) {
next = n;
}
return next;
}
}
// Returns the offset of the previous element. It is calculated based on
// the size of the map.
// To simplify implementation, offset must be 1-based
// return value is also 1-based.
template <typename size_type>
size_type prev(size_type offset, size_type size) {
auto prev = 2 * offset;
if (prev <= size) {
for (auto p = 2 * prev + 1; p <= size; p = 2 * p + 1) {
if (2 * p >= size) {
return p;
}
}
return prev;
}
return offset >> __builtin_ffsl((unsigned long)offset);
}
// To avoid scanning all offsets, skip offsets that cannot be within the
// range of offset1 and offset2.
// Note We could compute least common ancestor of offset1 and offset2
// to further minimize scanning. However it is not profitable when container
// is relatively small.
template <typename size_type>
size_type getStartOffsetToScan(size_type offset1, size_type offset2) {
while ((offset1 & (offset1 - 1)) != 0) {
offset1 >>= 1;
}
if (offset1 > 1) {
while ((offset2 & (offset2 - 1)) != 0) {
offset2 >>= 1;
}
offset1 = std::min(offset1, offset2);
}
return offset1;
}
// Given a start and end offsets, returns the distance between
// their corresponding indexes.
template <typename Container, typename size_type>
typename Container::difference_type distance(
Container& cont, size_type start, size_type end) {
using difference_type = typename Container::difference_type;
difference_type dist = 0;
size_type size = cont.size();
// To simplify logic base start and end from one.
start++;
end++;
std::function<bool(size_type, size_type)> calculateDistance =
[&](size_type offset, size_type lb) {
if (offset > size)
return false;
for (; offset <= size; offset <<= 1)
;
offset >>= 1;
for (; offset > lb; offset >>= 1) {
if (offset == start) {
if (dist) {
dist *= -1;
return true;
}
dist = 1;
} else if (offset == end) {
if (dist) {
return true;
}
dist = 1;
} else if (dist) {
dist++;
}
if (calculateDistance(2 * offset + 1, offset)) {
return true;
}
}
return false;
};
auto offset = getStartOffsetToScan(start, end);
calculateDistance(offset, size_type(0));
// Handle start == end()
if (start >= size) {
dist *= -1;
}
return dist;
}
// Returns the offset for each index in heap container
// for example if size = 7 then
// index 0 1 2 3 4 5 6
// offsets = { 3, 1, 4, 0, 5, 2, 6 }
// The smallest element (index = 0) of heap container is stored at cont[3] and
// so on.
template <typename size_type, typename Offsets>
static size_type getOffsets(
size_type size, Offsets& offsets, size_type index, size_type offset = 1) {
for (; offset <= size; offset <<= 1) {
index = getOffsets(size, offsets, index, 2 * offset + 1) - 1;
offsets[index] = offset - 1;
}
return index;
}
template <typename size_type, typename Offsets>
void getOffsets(size_type size, Offsets& offsets) {
getOffsets(size, offsets, size);
}
// Inplace conversion of a sorted vector to heap layout
// This algorithm utilizes circular swaps to position each element in its heap
// order offset in the vector. For example, given a sorted vector below:
// cont = { 0, 10, 20, 30, 40, 50, 60, 70 }
// getOffsets returns:
// index 0 1 2 3 4 5 6 7
// offsets = { 4, 2, 6, 1, 3, 5, 7, 0 }
//
// The algorithm moves elements circularly:
// cont[4]->cont[0]->cont[7]->cont[6]->cont[2]->cont[1]->cont[3]-> cont[4]
// cont[5] remains inplace
// returns:
// cont = { 40, 20, 60, 10, 30, 50, 70, 0 }
template <class Container>
void heapify(Container& cont) {
using size_type = typename Container::size_type;
size_type size = cont.size();
std::vector<size_type> offsets;
offsets.resize(size);
getOffsets(size, offsets);
std::function<void(size_type, size_type)> rotate = [&](size_type next,
size_type index) {
if (index == next)
return;
rotate(offsets[next], index);
cont[offsets[next]] = std::move(cont[next]);
offsets[next] = size;
};
for (size_type index = 0; index < size; index++) {
// already moved
if (offsets[index] == size) {
continue;
}
size_type next = offsets[index];
if (next == index) {
continue;
}
auto tmp = std::move(cont[index]);
rotate(next, index);
cont[next] = std::move(tmp);
}
}
// Below helper functions to implement inplace insertion/deletion.
// Returns the sequence of offsets that need to be moved. This sequence
// is the range between size-1 and the offset of the inserted element.
// There are two cases: {size - 1, ..., offset} or {offset, ..., size - 1}
// For example if 45 is inserted at offset == 0 and size == 9.
// Before insertion:
// element 0 10 20 30 40 50 60 70
// offset 7 3 1 4 0 5 2 6
// After inserting 45:
// element 0 10 20 30 40 45 50 60 70
// offset 7 3 8 1 4 0 5 2 6
// This function returns:
// offsets = { 8, 1, 4, 0 }
template <typename size_type, typename Offsets>
bool getOffsetRange(
size_type size, Offsets& offsets, size_type offset, size_type current) {
for (; current <= size; current <<= 1) {
if (getOffsetRange(size, offsets, offset, 2 * current + 1)) {
return true;
}
if (offsets.empty()) {
if (offset == current || size == current) {
// Start recording offsets that need to be moved.
offsets.push_back(current - 1);
}
} else {
// record offset
offsets.push_back(current - 1);
if (offset == current || size == current) {
// Stop recording offsets
return true;
}
}
}
return false;
}
template <typename size_type, typename Offsets>
void getOffsetRange(size_type size, Offsets& offsets, size_type offset) {
auto start = getStartOffsetToScan(offset, size);
getOffsetRange(size, offsets, offset, start);
}
// Insert a new element in heap order
// Assumption: the inserted element is already pushed at the back of the
// container vector (i.e. located at vector[size - 1]).
template <typename size_type, class Container>
size_type insert(size_type offset, Container& cont) {
size_type size = cont.size();
if (size == 1) {
return 0;
}
auto adjust = 1;
if (offset == size - 1) {
adjust = 0;
auto last = lastOffset(size);
if (last == offset) {
return offset;
}
offset = last;
}
std::vector<size_type> offsets;
offsets.reserve(size);
getOffsetRange(size, offsets, offset + 1);
typename Container::value_type v = std::move(cont[size - 1]);
if (offsets[0] != offset) {
for (size_type i = 1, e = offsets.size(); i < e; ++i) {
cont[offsets[i - 1]] = std::move(cont[offsets[i]]);
}
cont[offset] = std::move(v);
return offset;
}
for (size_type i = offsets.size() - 1; i > adjust; --i) {
cont[offsets[i]] = std::move(cont[offsets[i - 1]]);
}
cont[offsets[adjust]] = std::move(v);
return offsets[adjust];
}
// Erase one element and preserve heap order
template <typename size_type, class Container>
size_type erase(size_type offset, Container& cont) {
size_type size = cont.size();
if (offset + 1 == size) {
auto ret = next(offset + 1, size);
cont.resize(size - 1);
return ret ? ret - 1 : size - 1;
}
std::vector<size_type> offsets;
offsets.reserve(size);
getOffsetRange(size, offsets, offset + 1);
if (offsets[0] == offset) {
for (size_type i = 1, e = offsets.size(); i < e; i++) {
cont[offsets[i - 1]] = std::move(cont[offsets[i]]);
}
} else {
for (size_type i = offsets.size() - 1; i > 0; --i) {
cont[offsets[i]] = std::move(cont[offsets[i - 1]]);
}
}
cont.resize(size - 1);
return offset;
}
// Search lower bound in a container sorted in heap order.
// To speed up lower_bound for small containers, peel four iterations and use
// reverse compare to exit quickly.
// The branch inside the loop is converted to a cmov by the compiler. cmov are
// more efficient when the branch is unpredictable.
template <typename Compare, typename RCompare, typename Container>
typename Container::size_type lower_bound(
Container& cont, Compare cmp, RCompare reverseCmp) {
using size_type = typename Container::size_type;
size_type size = cont.size();
auto last = size;
size_type offset = 0;
if (size) {
if (cmp(cont[offset])) {
offset = 2 * offset + 2;
} else {
if (!reverseCmp(cont[offset])) {
return offset;
}
last = offset;
offset = 2 * offset + 1;
}
if (offset < size) {
if (cmp(cont[offset])) {
offset = 2 * offset + 2;
} else {
if (!reverseCmp(cont[offset])) {
return offset;
}
last = offset;
offset = 2 * offset + 1;
}
if (offset < size) {
if (cmp(cont[offset])) {
offset = 2 * offset + 2;
} else {
if (!reverseCmp(cont[offset])) {
return offset;
}
last = offset;
offset = 2 * offset + 1;
}
if (offset < size) {
if (cmp(cont[offset])) {
offset = 2 * offset + 2;
} else {
if (!reverseCmp(cont[offset])) {
return offset;
}
last = offset;
offset = 2 * offset + 1;
}
for (; offset < size; offset++) {
if (cmp(cont[offset])) {
offset = 2 * offset + 1;
} else {
last = offset;
offset = 2 * offset;
}
}
}
}
}
}
return last;
}
template <typename Compare, typename Container>
typename Container::size_type upper_bound(Container& cont, Compare cmp) {
using size_type = typename Container::size_type;
auto size = cont.size();
auto last = size;
for (size_type offset = 0; offset < size; offset++) {
if (!cmp(cont[offset])) {
offset = 2 * offset + 1;
} else {
last = offset;
offset = 2 * offset;
}
}
return last;
}
// Helper functions below are similar to sorted containers. Wherever
// applicable renamed to heap containers.
template <typename, typename Compare, typename Key, typename T>
struct heap_vector_enable_if_is_transparent {};
template <typename Compare, typename Key, typename T>
struct heap_vector_enable_if_is_transparent<
void_t<typename Compare::is_transparent>,
Compare,
Key,
T> {
using type = T;
};
// This wrapper goes around a GrowthPolicy and provides iterator
// preservation semantics, but only if the growth policy is not the
// default (i.e. nothing).
template <class Policy>
struct growth_policy_wrapper : private Policy {
template <class Container, class Iterator>
Iterator increase_capacity(Container& c, Iterator desired_insertion) {
using diff_t = typename Container::difference_type;
diff_t d = desired_insertion - c.begin();
Policy::increase_capacity(c);
return c.begin() + d;
}
};
template <>
struct growth_policy_wrapper<void> {
template <class Container, class Iterator>
Iterator increase_capacity(Container&, Iterator it) {
return it;
}
};
/*
* This helper returns the distance between two iterators if it is
* possible to figure it out without messing up the range
* (i.e. unless they are InputIterators). Otherwise this returns
* -1.
*/
template <class Iterator>
typename std::iterator_traits<Iterator>::difference_type distance_if_multipass(
Iterator first, Iterator last) {
using categ = typename std::iterator_traits<Iterator>::iterator_category;
if (std::is_same<categ, std::input_iterator_tag>::value) {
return -1;
}
return std::distance(first, last);
}
// Assumption fisrt != last
template <class OurContainer, class Container, class InputIterator>
void bulk_insert(
OurContainer& sorted,
Container& cont,
InputIterator first,
InputIterator last) {
auto const& cmp(sorted.value_comp());
auto const d = distance_if_multipass(first, last);
if (d != -1) {
cont.reserve(cont.size() + d);
}
auto const prev_size = cont.size();
std::copy(first, last, std::back_inserter(cont));
auto const middle = cont.begin() + prev_size;
if (!std::is_sorted(middle, cont.end(), cmp)) {
std::sort(middle, cont.end(), cmp);
}
if (middle != cont.begin() && !cmp(*(middle - 1), *middle)) {
std::inplace_merge(cont.begin(), middle, cont.end(), cmp);
}
cont.erase(
std::unique(
cont.begin(),
cont.end(),
[&](typename OurContainer::value_type const& a,
typename OurContainer::value_type const& b) {
return !cmp(a, b) && !cmp(b, a);
}),
cont.end());
heapify(cont);
}
template <typename Container, typename Compare>
bool is_sorted_unique(Container const& container, Compare const& comp) {
if (container.empty()) {
return true;
}
auto const e = container.end();
for (auto a = container.begin(), b = std::next(a); b != e; ++a, ++b) {
if (!comp(*a, *b)) {
return false;
}
}
return true;
}
template <typename Container, typename Compare>
Container&& as_sorted_unique(Container&& container, Compare const& comp) {
std::sort(container.begin(), container.end(), comp);
container.erase(
std::unique(
container.begin(),
container.end(),
[&](auto const& a, auto const& b) {
return !comp(a, b) && !comp(b, a);
}),
container.end());
return static_cast<Container&&>(container);
}
// class value_compare_map is used to compare map elements.
template <class Compare, class value_type>
struct value_compare_map : Compare {
bool operator()(const value_type& a, const value_type& b) const {
return Compare::operator()(a.first, b.first);
}
using first_type = typename value_type::first_type;
first_type& getKey(const value_type& a) const {
return const_cast<value_type&>(a).first;
}
first_type& getKey(const value_type& a) {
return const_cast<value_type&>(a).first;
}
explicit value_compare_map(const Compare& c) : Compare(c) {}
};
// wrapper class value_compare_set for set elements.
template <class Compare, class value_type>
struct value_compare_set : Compare {
bool operator()(const value_type& a, const value_type& b) const {
return Compare::operator()(a, b);
}
value_type& getKey(const value_type& a) const {
return const_cast<value_type&>(a);
}
value_type& getKey(const value_type& a) { return const_cast<value_type&>(a); }
explicit value_compare_set(const Compare& c) : Compare(c) {}
};
/**
* A heap_vector_container is a container similar to std::set<>, but
* implemented as a heap array with std::vector<>.
* This class contains shared implementation between set and map. It
* fully implements set methods and used as base class for map.
*
* @tparam T Data type to store
* @tparam Compare Comparison function that imposes a
* strict weak ordering over instances of T
* @tparam Allocator allocation policy
* @tparam GrowthPolicy policy object to control growth
* @tparam Container underlying vector where elements are stored
* @tparam KeyT key type, for set it is same as T.
* @tparam ValueCompare wrapper class to compare Container::value_type
*
* @author Zino Benaissa <zinob@fb.com>
*/
template <
class T,
class Compare = std::less<T>,
class Allocator = std::allocator<T>,
class GrowthPolicy = void,
class Container = std::vector<T, Allocator>,
class KeyT = T,
class ValueCompare = value_compare_set<Compare, T>>
class heap_vector_container : growth_policy_wrapper<GrowthPolicy> {
protected:
growth_policy_wrapper<GrowthPolicy>& get_growth_policy() { return *this; }
template <typename K, typename V, typename C = Compare>
using if_is_transparent =
_t<heap_vector_enable_if_is_transparent<void, C, K, V>>;
struct EBO;
public:
using key_type = KeyT;
using value_type = T;
using key_compare = Compare;
using value_compare = ValueCompare;
using allocator_type = Allocator;
using container_type = Container;
using pointer = typename Container::pointer;
using reference = typename Container::reference;
using const_reference = typename Container::const_reference;
using difference_type = typename Container::difference_type;
using size_type = typename Container::size_type;
// Defines inorder iterator for heap set.
template <typename Iter>
struct heap_iterator {
using iterator_category = std::random_access_iterator_tag;
using size_type = typename Container::size_type;
using difference_type = typename Container::difference_type;
using value_type = typename Container::value_type;
using pointer = typename Container::pointer;
using reference = typename Container::reference;
using const_reference = typename Container::const_reference;
heap_iterator() = default;
template <typename C>
heap_iterator(Iter ptr, C* cont) {
ptr_ = ptr;
cont_ = const_cast<Container*>(cont);
}
template <
typename I2,
typename = typename std::enable_if<
std::is_same<typename Container::iterator, I2>::value>::type>
/* implicit */ heap_iterator(const heap_iterator<I2>& rawIterator)
: ptr_(rawIterator.ptr_), cont_(rawIterator.cont_) {}
~heap_iterator() = default;
heap_iterator(const heap_iterator& rawIterator) = default;
heap_iterator& operator=(const heap_iterator& rawIterator) = default;
heap_iterator& operator=(Iter ptr) {
assert(
(ptr - cont_->begin()) >= 0 &&
(ptr - cont_->begin()) <= (difference_type)cont_->size());
ptr_ = ptr;
return (*this);
}
bool operator==(const heap_iterator& rawIterator) const {
return ptr_ == rawIterator.ptr_;
}
bool operator!=(const heap_iterator& rawIterator) const {
return !operator==(rawIterator);
}
heap_iterator& operator+=(const difference_type& movement) {
size_type offset = ptr_ - cont_->begin() + 1;
auto size = cont_->size();
if (movement < 0) {
difference_type i = 0;
if (offset - 1 == size) {
// handle --end()
offset = heap_vector_detail::lastOffset(size) + 1;
i = -1;
}
for (; i > movement; i--) {
offset = heap_vector_detail::prev(offset, size);
}
} else {
for (difference_type i = 0; i < movement; i++) {
offset = heap_vector_detail::next(offset, size);
}
}
ptr_ = cont_->begin() + (offset == 0 ? cont_->size() : offset - 1);
return (*this);
}
heap_iterator& operator-=(const difference_type& movement) {
return operator+=(-movement);
}
heap_iterator& operator++() { return operator+=(1); }
heap_iterator& operator--() { return operator-=(1); }
heap_iterator operator++(int) {
auto temp(*this);
operator+=(1);
return temp;
}
heap_iterator operator--(int) {
auto temp(*this);
operator-=(1);
return temp;
}
heap_iterator operator+(const difference_type& movement) {
auto temp(*this);
temp += movement;
return temp;
}
heap_iterator operator+(const difference_type& movement) const {
auto temp(*this);
temp += movement;
return temp;
}
heap_iterator operator-(const difference_type& movement) {
auto temp(*this);
temp -= movement;
return temp;
}
heap_iterator operator-(const difference_type& movement) const {
auto temp(*this);
temp -= movement;
return temp;
}
difference_type operator-(const heap_iterator& rawIterator) {
assert(cont_ == rawIterator.cont_);
size_type offset0 = ptr_ - cont_->begin();
size_type offset1 = rawIterator.ptr_ - cont_->begin();
if (offset1 == offset0)
return 0;
return heap_vector_detail::distance(*cont_, offset1, offset0);
}
difference_type operator-(const heap_iterator& rawIterator) const {
assert(cont_ == rawIterator.cont_);
size_type offset0 = ptr_ - cont_->begin();
size_type offset1 = rawIterator.ptr_ - cont_->begin();
if (offset1 == offset0)
return 0;
return heap_vector_detail::distance(*cont_, offset1, offset0);
}
reference operator*() { return const_cast<reference>(*ptr_); }
reference operator*() const { return const_cast<reference>(*ptr_); }
pointer operator->() { return &const_cast<reference>(*ptr_); }
pointer operator->() const { return &const_cast<reference>(*ptr_); }
protected:
template <typename I2>
friend struct heap_iterator;
Iter ptr_;
Container* cont_;
};
using iterator = heap_iterator<typename Container::iterator>;
using const_iterator = heap_iterator<typename Container::const_iterator>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
heap_vector_container() : m_(value_compare(Compare()), Allocator()) {}
heap_vector_container(const heap_vector_container&) = default;
heap_vector_container(
const heap_vector_container& other, const Allocator& alloc)
: m_(other.m_, alloc) {}
heap_vector_container(heap_vector_container&&) = default;
heap_vector_container(
heap_vector_container&& other,
const Allocator& alloc) noexcept(std::
is_nothrow_constructible<
EBO,
EBO&&,
const Allocator&>::value)
: m_(std::move(other.m_), alloc) {}
explicit heap_vector_container(const Allocator& alloc)
: m_(value_compare(Compare()), alloc) {}
explicit heap_vector_container(
const Compare& comp, const Allocator& alloc = Allocator())
: m_(value_compare(comp), alloc) {}
template <class InputIterator>
explicit heap_vector_container(
InputIterator first,
InputIterator last,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator())
: m_(value_compare(comp), alloc) {
insert(first, last);
}
template <class InputIterator>
heap_vector_container(
InputIterator first, InputIterator last, const Allocator& alloc)
: m_(value_compare(Compare()), alloc) {
insert(first, last);
}
/* implicit */ heap_vector_container(
std::initializer_list<value_type> list,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator())
: m_(value_compare(comp), alloc) {
insert(list.begin(), list.end());
}
heap_vector_container(
std::initializer_list<value_type> list, const Allocator& alloc)
: m_(value_compare(Compare()), alloc) {
insert(list.begin(), list.end());
}
// Construct a heap_vector_container by stealing the storage of a prefilled
// container. The container need not be sorted already. This supports
// bulk construction of heap_vector_container with zero allocations, not
// counting those performed by the caller.
// Note that `heap_vector_container(const Container& container)` is not
// provided, since the purpose of this constructor is to avoid an unnecessary
// copy.
explicit heap_vector_container(
Container&& container,
const Compare& comp = Compare()) noexcept(std::
is_nothrow_constructible<
EBO,
value_compare,
Container&&>::value)
: heap_vector_container(
sorted_unique,
heap_vector_detail::as_sorted_unique(
std::move(container), value_compare(comp)),
comp) {}
// Construct a heap_vector_container by stealing the storage of a prefilled
// container. Its elements must be sorted and unique, as sorted_unique_t
// hints. Supports bulk construction of heap_vector_container with zero
// allocations, not counting those performed by the caller.
// Note that `heap_vector_container(sorted_unique_t, const Container&
// container)` is not provided, since the purpose of this constructor is to
// avoid an extra copy.
heap_vector_container(
sorted_unique_t /* unused */,
Container&& container,
const Compare& comp = Compare()) noexcept(std::
is_nothrow_constructible<
EBO,
value_compare,
Container&&>::value)
: m_(value_compare(comp), std::move(container)) {
assert(heap_vector_detail::is_sorted_unique(m_.cont_, value_comp()));
heap_vector_detail::heapify(m_.cont_);
}
Allocator get_allocator() const { return m_.cont_.get_allocator(); }
heap_vector_container& operator=(const heap_vector_container& other) =
default;
heap_vector_container& operator=(heap_vector_container&& other) = default;
heap_vector_container& operator=(std::initializer_list<value_type> ilist) {
clear();
insert(ilist.begin(), ilist.end());
return *this;
}
key_compare key_comp() const { return m_; }
value_compare value_comp() const { return m_; }
iterator begin() {
if (size()) {
return iterator(
m_.cont_.begin() + heap_vector_detail::firstOffset(size()),
&m_.cont_);
}
return iterator(m_.cont_.begin(), &m_.cont_);
}
iterator end() { return iterator(m_.cont_.end(), &m_.cont_); }
const_iterator cbegin() const {
if (size()) {
return const_iterator(
m_.cont_.cbegin() + heap_vector_detail::firstOffset(size()),
&m_.cont_);
}
return const_iterator(m_.cont_.cbegin(), &m_.cont_);
}
const_iterator begin() const {
if (size()) {
return const_iterator(
m_.cont_.cbegin() + heap_vector_detail::firstOffset(size()),
&m_.cont_);
}
return const_iterator(m_.cont_.begin(), &m_.cont_);
}
const_iterator cend() const {
return const_iterator(m_.cont_.cend(), &m_.cont_);
}
const_iterator end() const {
return const_iterator(m_.cont_.end(), &m_.cont_);
}
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
const_reverse_iterator crbegin() const {
return const_reverse_iterator(end());
}
const_reverse_iterator crend() const {
return const_reverse_iterator(begin());
}
void clear() { return m_.cont_.clear(); }
size_type size() const { return m_.cont_.size(); }
size_type max_size() const { return m_.cont_.max_size(); }
bool empty() const { return m_.cont_.empty(); }
void reserve(size_type s) { return m_.cont_.reserve(s); }
void shrink_to_fit() { m_.cont_.shrink_to_fit(); }
size_type capacity() const { return m_.cont_.capacity(); }
std::pair<iterator, bool> insert(const value_type& value) {
iterator it = lower_bound(m_.getKey(value));
if (it == end() || value_comp()(value, *it)) {
auto offset = &*it - &*m_.cont_.begin();
get_growth_policy().increase_capacity(*this, it);
m_.cont_.push_back(value);
offset = heap_vector_detail::insert(offset, m_.cont_);
it = m_.cont_.begin() + offset;
return std::make_pair(it, true);
}
return std::make_pair(it, false);
}
std::pair<iterator, bool> insert(value_type&& value) {
iterator it = lower_bound(m_.getKey(value));
if (it == end() || value_comp()(value, *it)) {
auto offset = &*it - &*m_.cont_.begin();
get_growth_policy().increase_capacity(*this, it);
m_.cont_.push_back(std::move(value));
offset = heap_vector_detail::insert(offset, m_.cont_);
it = m_.cont_.begin() + offset;
return std::make_pair(it, true);
}
return std::make_pair(it, false);
}
/* There is no benefit of using hint. Keep it for compatibility
* Ignore and insert */
iterator insert(const_iterator /* hint */, const value_type& value) {
return insert(value).first;
}
iterator insert(const_iterator /* hint */, value_type&& value) {
return insert(std::move(value)).first;
}
template <class InputIterator>
void insert(InputIterator first, InputIterator last) {
if (first == last) {
return;
}
auto const d = heap_vector_detail::distance_if_multipass(first, last);
if (d == 1) {
insert(*first);
return;
}
std::sort(m_.cont_.begin(), m_.cont_.end(), value_comp());
heap_vector_detail::bulk_insert(*this, m_.cont_, first, last);
}
void insert(std::initializer_list<value_type> ilist) {
insert(ilist.begin(), ilist.end());
}
// emplace isn't better than insert for heap_vector_container, but aids
// compatibility
template <typename... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
std::aligned_storage_t<sizeof(value_type), alignof(value_type)> b;
auto* p = static_cast<value_type*>(static_cast<void*>(&b));
auto a = get_allocator();
std::allocator_traits<allocator_type>::construct(
a, p, std::forward<Args>(args)...);
auto g = makeGuard(
[&]() { std::allocator_traits<allocator_type>::destroy(a, p); });
return insert(std::move(*p));
}
std::pair<iterator, bool> emplace(const value_type& value) {
return insert(value);
}
std::pair<iterator, bool> emplace(value_type&& value) {
return insert(std::move(value));
}
// emplace_hint isn't better than insert for heap_vector_container, but aids
// compatibility
template <typename... Args>
iterator emplace_hint(const_iterator /* hint */, Args&&... args) {
return emplace(std::forward<Args>(args)...).first;
}
iterator emplace_hint(const_iterator hint, const value_type& value) {
return insert(hint, value);
}
iterator emplace_hint(const_iterator hint, value_type&& value) {
return insert(hint, std::move(value));
}
size_type erase(const key_type& key) {
iterator it = find(key);
if (it == end()) {
return 0;
}
heap_vector_detail::erase(&*it - &*m_.cont_.begin(), m_.cont_);
return 1;
}
iterator erase(const_iterator it) {
auto offset =
heap_vector_detail::erase(&*it - &*m_.cont_.begin(), m_.cont_);
iterator ret = end();
ret = m_.cont_.begin() + offset;
return ret;
}
iterator erase(const_iterator first, const_iterator last) {
if (first == last) {
return end();
}
auto dist = last - first;
if (dist <= 0) {
return end();
}
if (dist == 1) {
return erase(first);
}
auto it = m_.cont_.begin() + (first - begin());
std::sort(m_.cont_.begin(), m_.cont_.end(), value_comp());
it = m_.cont_.erase(it, it + dist);
heap_vector_detail::heapify(m_.cont_);
return begin() + (it - m_.cont_.begin());
}
iterator find(const key_type& key) { return find_(*this, key); }
const_iterator find(const key_type& key) const { return find_(*this, key); }
template <typename K>
if_is_transparent<K, iterator> find(const K& key) {
return find_(*this, key);
}
template <typename K>
if_is_transparent<K, const_iterator> find(const K& key) const {
return find_(*this, key);
}
size_type count(const key_type& key) const {
return find(key) == end() ? 0 : 1;
}
template <typename K>
if_is_transparent<K, size_type> count(const K& key) const {
return find(key) == end() ? 0 : 1;
}
bool contains(const key_type& key) const { return find(key) != end(); }
template <typename K>
if_is_transparent<K, bool> contains(const K& key) const {
return find(key) != end();
}
iterator lower_bound(const key_type& key) { return lower_bound(*this, key); }
const_iterator lower_bound(const key_type& key) const {
return lower_bound(*this, key);
}
template <typename K>
if_is_transparent<K, iterator> lower_bound(const K& key) {
return lower_bound(*this, key);
}
template <typename K>
if_is_transparent<K, const_iterator> lower_bound(const K& key) const {
return lower_bound(*this, key);
}
iterator upper_bound(const key_type& key) { return upper_bound(*this, key); }
const_iterator upper_bound(const key_type& key) const {
return upper_bound(*this, key);
}
template <typename K>
if_is_transparent<K, iterator> upper_bound(const K& key) {
return upper_bound(*this, key);
}
template <typename K>
if_is_transparent<K, const_iterator> upper_bound(const K& key) const {
return upper_bound(*this, key);
}
std::pair<iterator, iterator> equal_range(const key_type& key) {
return {lower_bound(key), upper_bound(key)};
}
std::pair<const_iterator, const_iterator> equal_range(
const key_type& key) const {
return {lower_bound(key), upper_bound(key)};
}
template <typename K>
if_is_transparent<K, std::pair<iterator, iterator>> equal_range(
const K& key) {
return {lower_bound(key), upper_bound(key)};
}
template <typename K>
if_is_transparent<K, std::pair<const_iterator, const_iterator>> equal_range(
const K& key) const {
return {lower_bound(key), upper_bound(key)};
}
void swap(heap_vector_container& o) noexcept(
std::is_nothrow_swappable<Compare>::value&& noexcept(
Container().swap(m_.cont_))) {
using std::swap; // Allow ADL for swap(); fall back to std::swap().
Compare& a = m_;
Compare& b = o.m_;
swap(a, b);
m_.cont_.swap(o.m_.cont_);
}
bool operator==(const heap_vector_container& other) const {
return m_.cont_ == other.m_.cont_;
}
bool operator!=(const heap_vector_container& other) const {
return !operator==(other);
}
bool operator<(const heap_vector_container& other) const {
return std::lexicographical_compare(
begin(), end(), other.begin(), other.end(), value_comp());
}
bool operator>(const heap_vector_container& other) const {
return other < *this;
}
bool operator<=(const heap_vector_container& other) const {
return !operator>(other);
}
bool operator>=(const heap_vector_container& other) const {
return !operator<(other);
}
// Use underlying vector iterators to quickly traverse heap container.
// Note elements are traversed following the heap order, i.e., memory
// storage order.
Range<typename Container::iterator> iterate() noexcept {
return Range<typename Container::iterator>(
m_.cont_.begin(), m_.cont_.end());
}
const Range<typename Container::iterator> iterate() const noexcept {
return Range<typename Container::iterator>(
m_.cont_.begin(), m_.cont_.end());
}
protected:
// This is to get the empty base optimization
struct EBO : value_compare {
explicit EBO(const value_compare& c, const Allocator& alloc) noexcept(
std::is_nothrow_default_constructible<Container>::value)
: value_compare(c), cont_(alloc) {}
EBO(const EBO& other, const Allocator& alloc)
noexcept(std::is_nothrow_constructible<
Container,
const Container&,
const Allocator&>::value)
: value_compare(static_cast<const value_compare&>(other)),
cont_(other.cont_, alloc) {}
EBO(EBO&& other, const Allocator& alloc)
noexcept(std::is_nothrow_constructible<
Container,
Container&&,
const Allocator&>::value)
: value_compare(static_cast<value_compare&&>(other)),
cont_(std::move(other.cont_), alloc) {}
EBO(const Compare& c, Container&& cont)
noexcept(std::is_nothrow_move_constructible<Container>::value)
: value_compare(c), cont_(std::move(cont)) {}
Container cont_;
} m_;
template <typename Self>
using self_iterator_t = typename std::
conditional<std::is_const<Self>::value, const_iterator, iterator>::type;
template <typename Self, typename K>
static self_iterator_t<Self> find_(Self& self, K const& key) {
self_iterator_t<Self> end = self.end();
self_iterator_t<Self> it = self.lower_bound(key);
if (it == end || !self.key_comp()(key, self.m_.getKey(*it))) {
return it;
}
return end;
}
template <typename Self, typename K>
static self_iterator_t<Self> lower_bound(Self& self, K const& key) {
auto c = self.key_comp();
auto cmp = [&](value_type const& a) { return c(self.m_.getKey(a), key); };
auto reverseCmp = [&](value_type const& a) {
return c(key, self.m_.getKey(a));
};
auto offset =
heap_vector_detail::lower_bound(self.m_.cont_, cmp, reverseCmp);
self_iterator_t<Self> ret = self.end();
ret = self.m_.cont_.begin() + offset;
return ret;
}
template <typename Self, typename K>
static self_iterator_t<Self> upper_bound(Self& self, K const& key) {
auto c = self.key_comp();
auto cmp = [&](value_type const& a) { return c(key, self.m_.getKey(a)); };
auto offset = heap_vector_detail::upper_bound(self.m_.cont_, cmp);
self_iterator_t<Self> ret = self.end();
ret = self.m_.cont_.begin() + offset;
return ret;
}
};
} // namespace heap_vector_detail
} // namespace detail
/* heap_vector_set is a specialization of heap_vector_container
*
* @tparam T Data type to store
* @tparam Compare Comparison function that imposes a
* strict weak ordering over instances of T
* @tparam Allocator allocation policy
* @tparam GrowthPolicy policy object to control growth
* @tparam Container underlying vector where elements are stored
*/
template <
class T,
class Compare = std::less<T>,
class Allocator = std::allocator<T>,
class GrowthPolicy = void,
class Container = std::vector<T, Allocator>>
using heap_vector_set = detail::heap_vector_detail::heap_vector_container<
T,
Compare,
Allocator,
GrowthPolicy,
Container,
T,
detail::heap_vector_detail::value_compare_set<Compare, T>>;
// Swap function that can be found using ADL.
template <class T, class C, class A, class G>
inline void swap(
heap_vector_set<T, C, A, G>& a, heap_vector_set<T, C, A, G>& b) noexcept {
return a.swap(b);
}
#if FOLLY_HAS_MEMORY_RESOURCE
namespace pmr {
template <
class T,
class Compare = std::less<T>,
class GrowthPolicy = void,
class Container =
std::vector<T, folly::detail::std_pmr::polymorphic_allocator<T>>>
using heap_vector_set = folly::heap_vector_set<
T,
Compare,
folly::detail::std_pmr::polymorphic_allocator<T>,
GrowthPolicy,
Container>;
} // namespace pmr
#endif
//////////////////////////////////////////////////////////////////////
/**
* A heap_vector_map based on heap layout.
*
* @tparam Key Key type
* @tparam Value Value type
* @tparam Compare Function that can compare key types and impose
* a strict weak ordering over them.
* @tparam Allocator allocation policy
* @tparam GrowthPolicy policy object to control growth
*
* @author Zino Benaissa <zinob@fb.com>
*/
template <
class Key,
class Value,
class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<Key, Value>>,
class GrowthPolicy = void,
class Container = std::vector<std::pair<Key, Value>, Allocator>>
class heap_vector_map
: public detail::heap_vector_detail::heap_vector_container<
typename Container::value_type,
Compare,
Allocator,
GrowthPolicy,
Container,
Key,
detail::heap_vector_detail::
value_compare_map<Compare, typename Container::value_type>> {
public:
using key_type = Key;
using mapped_type = Value;
using value_type = typename Container::value_type;
using key_compare = Compare;
using allocator_type = Allocator;
using container_type = Container;
using pointer = typename Container::pointer;
using reference = typename Container::reference;
using const_reference = typename Container::const_reference;
using difference_type = typename Container::difference_type;
using size_type = typename Container::size_type;
using value_compare =
detail::heap_vector_detail::value_compare_map<Compare, value_type>;
private:
using heap_vector_container =
detail::heap_vector_detail::heap_vector_container<
value_type,
key_compare,
Allocator,
GrowthPolicy,
Container,
key_type,
value_compare>;
using heap_vector_container::get_growth_policy;
using heap_vector_container::m_;
public:
using iterator = typename heap_vector_container::iterator;
using const_iterator = typename heap_vector_container::const_iterator;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// Since heap_vector_container methods are publicly available through
// inheritance, just expose method used within this class.
using heap_vector_container::end;
using heap_vector_container::find;
using heap_vector_container::heap_vector_container;
using heap_vector_container::key_comp;
using heap_vector_container::lower_bound;
mapped_type& at(const key_type& key) {
iterator it = find(key);
if (it != end()) {
return it->second;
}
throw_exception<std::out_of_range>("heap_vector_map::at");
}
const mapped_type& at(const key_type& key) const {
const_iterator it = find(key);
if (it != end()) {
return it->second;
}
throw_exception<std::out_of_range>("heap_vector_map::at");
}
mapped_type& operator[](const key_type& key) {
iterator it = lower_bound(key);
if (it == end() || key_comp()(key, it->first)) {
auto offset = &*it - &*m_.cont_.begin();
get_growth_policy().increase_capacity(*this, it);
m_.cont_.emplace_back(key, mapped_type());
offset = detail::heap_vector_detail::insert(offset, m_.cont_);
it = m_.cont_.begin() + offset;
return it->second;
}
return it->second;
}
};
// Swap function that can be found using ADL.
template <class K, class V, class C, class A, class G>
inline void swap(
heap_vector_map<K, V, C, A, G>& a,
heap_vector_map<K, V, C, A, G>& b) noexcept {
return a.swap(b);
}
#if FOLLY_HAS_MEMORY_RESOURCE
namespace pmr {
template <
class Key,
class Value,
class Compare = std::less<Key>,
class GrowthPolicy = void,
class Container = std::vector<
std::pair<Key, Value>,
folly::detail::std_pmr::polymorphic_allocator<std::pair<Key, Value>>>>
using heap_vector_map = folly::heap_vector_map<
Key,
Value,
Compare,
folly::detail::std_pmr::polymorphic_allocator<std::pair<Key, Value>>,
GrowthPolicy,
Container>;
} // namespace pmr
#endif
} // namespace folly
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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 <iterator>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <folly/Random.h>
#include <folly/Range.h>
#include <folly/Utility.h>
#include <folly/container/heap_vector_types.h>
#include <folly/memory/Malloc.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
#include <folly/small_vector.h>
#include <folly/sorted_vector_types.h>
using folly::heap_vector_map;
using folly::heap_vector_set;
namespace {
template <class T>
struct less_invert {
bool operator()(const T& a, const T& b) const { return b < a; }
};
template <class Container>
void check_invariant(Container& c) {
auto it = c.begin();
auto end = c.end();
if (it == end) {
return;
}
auto prev = it;
++it;
for (; it != end; ++it, ++prev) {
EXPECT_TRUE(c.value_comp()(*prev, *it));
}
}
struct OneAtATimePolicy {
template <class Container>
void increase_capacity(Container& c) {
if (c.size() == c.capacity()) {
c.reserve(c.size() + 1);
}
}
};
struct CountCopyCtor {
explicit CountCopyCtor() : val_(0), count_(0) {}
explicit CountCopyCtor(int val) : val_(val), count_(0) {}
CountCopyCtor(const CountCopyCtor& c) noexcept
: val_(c.val_), count_(c.count_ + 1) {
++gCount_;
}
CountCopyCtor& operator=(const CountCopyCtor&) = default;
bool operator<(const CountCopyCtor& o) const { return val_ < o.val_; }
int val_;
int count_;
static int gCount_;
};
int CountCopyCtor::gCount_ = 0;
struct KeyCopiedException : public std::exception {};
/**
* Key that may throw on copy when throwOnCopy is set, but never on move.
* Use clone() to copy without throwing.
*/
struct KeyThatThrowsOnCopies {
int32_t key{};
bool throwOnCopy{};
/* implicit */ KeyThatThrowsOnCopies(int32_t key) noexcept
: key(key), throwOnCopy(false) {}
KeyThatThrowsOnCopies(int32_t key, bool throwOnCopy) noexcept
: key(key), throwOnCopy(throwOnCopy) {}
~KeyThatThrowsOnCopies() noexcept {}
KeyThatThrowsOnCopies(KeyThatThrowsOnCopies const& other)
: key(other.key), throwOnCopy(other.throwOnCopy) {
if (throwOnCopy) {
throw KeyCopiedException{};
}
}
KeyThatThrowsOnCopies(KeyThatThrowsOnCopies&& other) noexcept = default;
KeyThatThrowsOnCopies& operator=(KeyThatThrowsOnCopies const& other) {
key = other.key;
throwOnCopy = other.throwOnCopy;
if (throwOnCopy) {
throw KeyCopiedException{};
}
return *this;
}
KeyThatThrowsOnCopies& operator=(KeyThatThrowsOnCopies&& other) noexcept =
default;
bool operator<(const KeyThatThrowsOnCopies& other) const {
return key < other.key;
}
};
static_assert(
std::is_nothrow_move_constructible<KeyThatThrowsOnCopies>::value &&
std::is_nothrow_move_assignable<KeyThatThrowsOnCopies>::value,
"non-noexcept move-constructible or move-assignable");
} // namespace
TEST(HeapVectorTypes, SetAssignmentInitListTest) {
heap_vector_set<int> s{3, 4, 5};
EXPECT_THAT(s, testing::ElementsAreArray({3, 4, 5}));
s = {}; // empty ilist assignment
EXPECT_THAT(s, testing::IsEmpty());
s = {7, 8, 9}; // non-empty ilist assignment
EXPECT_THAT(s, testing::ElementsAreArray({7, 8, 9}));
}
TEST(HeapVectorTypes, SimpleSetTest) {
heap_vector_set<int> s;
EXPECT_TRUE(s.empty());
for (int i = 0; i < 1000; ++i) {
s.insert(folly::Random::rand32() % 100000);
}
EXPECT_FALSE(s.empty());
check_invariant(s);
heap_vector_set<int> s2;
s2.insert(s.begin(), s.end());
check_invariant(s2);
EXPECT_TRUE(s == s2);
auto it = s2.lower_bound(32);
if (*it == 32) {
s2.erase(it);
it = s2.lower_bound(32);
}
check_invariant(s2);
auto oldSz = s2.size();
s2.insert(it, 32);
EXPECT_TRUE(s2.size() == oldSz + 1);
check_invariant(s2);
const heap_vector_set<int>& cs2 = s2;
auto range = cs2.equal_range(32);
auto lbound = cs2.lower_bound(32);
auto ubound = cs2.upper_bound(32);
EXPECT_TRUE(range.first == lbound);
EXPECT_TRUE(range.second == ubound);
EXPECT_TRUE(range.first != cs2.end());
EXPECT_TRUE(range.second != cs2.end());
EXPECT_TRUE(cs2.count(32) == 1);
EXPECT_FALSE(cs2.find(32) == cs2.end());
EXPECT_TRUE(cs2.contains(32));
// Bad insert hint.
s2.insert(s2.begin() + 3, 33);
EXPECT_TRUE(s2.find(33) != s2.begin());
EXPECT_TRUE(s2.find(33) != s2.end());
check_invariant(s2);
s2.erase(33);
check_invariant(s2);
it = s2.find(32);
EXPECT_FALSE(it == s2.end());
s2.erase(it);
EXPECT_FALSE(cs2.contains(32));
EXPECT_TRUE(s2.size() == oldSz);
check_invariant(s2);
heap_vector_set<int> cpy(s);
check_invariant(cpy);
EXPECT_TRUE(cpy == s);
heap_vector_set<int> cpy2(s);
cpy2.insert(100001);
EXPECT_TRUE(cpy2 != cpy);
EXPECT_TRUE(cpy2 != s);
check_invariant(cpy2);
EXPECT_TRUE(cpy2.count(100001) == 1);
s.swap(cpy2);
check_invariant(cpy2);
check_invariant(s);
EXPECT_TRUE(s != cpy);
EXPECT_TRUE(s != cpy2);
EXPECT_TRUE(cpy2 == cpy);
heap_vector_set<int> s3 = {};
s3.insert({1, 2, 3});
s3.emplace(4);
EXPECT_EQ(s3.size(), 4);
heap_vector_set<std::string> s4;
s4.emplace("foobar", 3);
EXPECT_EQ(s4.count("foo"), 1);
}
TEST(HeapVectorTypes, TransparentSetTest) {
using namespace folly::string_piece_literals;
using Compare = folly::transparent<std::less<folly::StringPiece>>;
constexpr auto buddy = "buddy"_sp;
constexpr auto hello = "hello"_sp;
constexpr auto stake = "stake"_sp;
constexpr auto world = "world"_sp;
constexpr auto zebra = "zebra"_sp;
heap_vector_set<std::string, Compare> const s({hello.str(), world.str()});
// find
EXPECT_TRUE(s.end() == s.find(buddy));
EXPECT_EQ(hello, *s.find(hello));
EXPECT_TRUE(s.end() == s.find(stake));
EXPECT_EQ(world, *s.find(world));
EXPECT_TRUE(s.end() == s.find(zebra));
// count
EXPECT_EQ(0, s.count(buddy));
EXPECT_EQ(1, s.count(hello));
EXPECT_EQ(0, s.count(stake));
EXPECT_EQ(1, s.count(world));
EXPECT_EQ(0, s.count(zebra));
// contains
EXPECT_FALSE(s.contains(buddy));
EXPECT_TRUE(s.contains(hello));
EXPECT_FALSE(s.contains(stake));
EXPECT_TRUE(s.contains(world));
EXPECT_FALSE(s.contains(zebra));
// lower_bound
EXPECT_TRUE(s.find(hello) == s.lower_bound(buddy));
EXPECT_TRUE(s.find(hello) == s.lower_bound(hello));
EXPECT_TRUE(s.find(world) == s.lower_bound(stake));
EXPECT_TRUE(s.find(world) == s.lower_bound(world));
EXPECT_TRUE(s.end() == s.lower_bound(zebra));
// upper_bound
EXPECT_TRUE(s.find(hello) == s.upper_bound(buddy));
EXPECT_TRUE(s.find(world) == s.upper_bound(hello));
EXPECT_TRUE(s.find(world) == s.upper_bound(stake));
EXPECT_TRUE(s.end() == s.upper_bound(world));
EXPECT_TRUE(s.end() == s.upper_bound(zebra));
// equal_range
for (auto value : {buddy, hello, stake, world, zebra}) {
EXPECT_TRUE(
std::make_pair(s.lower_bound(value), s.upper_bound(value)) ==
s.equal_range(value))
<< value;
}
}
TEST(HeapVectorTypes, BadHints) {
for (int toInsert = -1; toInsert <= 7; ++toInsert) {
for (int hintPos = 0; hintPos <= 4; ++hintPos) {
heap_vector_set<int> s;
for (int i = 0; i <= 3; ++i) {
s.insert(i * 2);
}
s.insert(s.begin() + hintPos, toInsert);
size_t expectedSize = (toInsert % 2) == 0 ? 4 : 5;
EXPECT_EQ(s.size(), expectedSize);
check_invariant(s);
}
}
}
TEST(HeapVectorTypes, MapAssignmentInitListTest) {
using v = std::pair<int, const char*>;
v p = {3, "a"}, q = {4, "b"}, r = {5, "c"};
heap_vector_map<int, const char*> m{p, q, r};
EXPECT_THAT(m, testing::ElementsAreArray({p, q, r}));
m = {}; // empty ilist assignment
EXPECT_THAT(m, testing::IsEmpty());
m = {p, q, r}; // non-empty ilist assignment
EXPECT_THAT(m, testing::ElementsAreArray({p, q, r}));
}
TEST(HeapVectorTypes, MapBadHints) {
for (int toInsert = -1; toInsert <= 7; ++toInsert) {
for (int hintPos = 0; hintPos <= 4; ++hintPos) {
heap_vector_map<int, int> s;
for (int i = 0; i <= 3; ++i) {
s.emplace(i * 2, i);
}
s.emplace_hint(s.begin() + hintPos, toInsert, toInsert);
size_t expectedSize = (toInsert % 2) == 0 ? 4 : 5;
EXPECT_EQ(s.size(), expectedSize);
check_invariant(s);
}
}
}
TEST(HeapVectorTypes, FromVector) {
folly::heap_vector_map<int, float>::container_type vec;
vec.push_back(std::make_pair(3, 3.0f));
vec.push_back(std::make_pair(1, 1.0f));
vec.push_back(std::make_pair(2, 2.0f));
heap_vector_map<int, float> m(std::move(vec));
EXPECT_EQ(vec.size(), 0);
EXPECT_EQ(m.size(), 3);
EXPECT_EQ(m[1], 1.0f);
EXPECT_EQ(m[2], 2.0f);
EXPECT_EQ(m[3], 3.0f);
}
TEST(HeapVectorTypes, IterateOverVectorWithinMap) {
const int size = 10;
folly::heap_vector_map<int, int> m;
int heap_order[size] = {6, 3, 8, 1, 5, 7, 9, 0, 2, 4};
for (int i = 0; i < size; i++) {
m[i] = i;
}
// Iterate over underlying container. Fastest
int i = 0;
for (auto& e : m.iterate()) {
EXPECT_EQ(e.second, heap_order[i++]);
}
// Iterate inorder using heap_vector_map iterator
i = 0;
for (auto& e : m) {
EXPECT_EQ(e.first, i++);
}
}
TEST(HeapVectorTypes, SimpleMapTest) {
heap_vector_map<int, float> m;
for (int i = 0; i < 1000; ++i) {
m[i] = float(i / 1000.0);
}
check_invariant(m);
m[32] = 100.0f;
check_invariant(m);
EXPECT_TRUE(m.count(32) == 1);
EXPECT_DOUBLE_EQ(100.0, m.at(32));
EXPECT_FALSE(m.find(32) == m.end());
EXPECT_TRUE(m.contains(32));
m.erase(32);
EXPECT_TRUE(m.find(32) == m.end());
EXPECT_FALSE(m.contains(32));
check_invariant(m);
EXPECT_THROW(m.at(32), std::out_of_range);
heap_vector_map<int, float> m2 = m;
EXPECT_TRUE(m2 == m);
EXPECT_FALSE(m2 != m);
auto it = m2.lower_bound(1 << 20);
EXPECT_TRUE(it == m2.end());
m2.insert(it, std::make_pair(1 << 20, 10.0f));
check_invariant(m2);
EXPECT_TRUE(m2.count(1 << 20) == 1);
EXPECT_TRUE(m < m2);
EXPECT_TRUE(m <= m2);
const heap_vector_map<int, float>& cm = m;
auto range = cm.equal_range(42);
auto lbound = cm.lower_bound(42);
auto ubound = cm.upper_bound(42);
EXPECT_TRUE(range.first == lbound);
EXPECT_TRUE(range.second == ubound);
EXPECT_FALSE(range.first == cm.end());
EXPECT_FALSE(range.second == cm.end());
m.erase(m.lower_bound(42));
check_invariant(m);
heap_vector_map<int, float> m3;
m3.insert(m2.begin(), m2.end());
check_invariant(m3);
EXPECT_TRUE(m3 == m2);
EXPECT_FALSE(m3 == m);
heap_vector_map<int, float> m4;
m4.emplace(1, 2.0f);
m4.emplace(3, 1.0f);
m4.emplace(2, 1.5f);
check_invariant(m4);
EXPECT_TRUE(m4.size() == 3);
heap_vector_map<int, float> m5;
for (auto& kv : m2) {
m5.emplace(kv);
}
check_invariant(m5);
EXPECT_TRUE(m5 == m2);
EXPECT_FALSE(m5 == m);
EXPECT_TRUE(m != m2);
EXPECT_TRUE(m2 == m3);
EXPECT_TRUE(m3 != m);
m.swap(m3);
check_invariant(m);
check_invariant(m2);
check_invariant(m3);
EXPECT_TRUE(m3 != m2);
EXPECT_TRUE(m3 != m);
EXPECT_TRUE(m == m2);
// Bad insert hint.
m.insert(m.begin() + 3, std::make_pair(1 << 15, 1.0f));
check_invariant(m);
heap_vector_map<int, float> m6 = {};
m6.insert({{1, 1.0f}, {2, 2.0f}, {1, 2.0f}});
EXPECT_EQ(m6.at(2), 2.0f);
}
TEST(HeapVectorTypes, TransparentMapTest) {
using namespace folly::string_piece_literals;
using Compare = folly::transparent<std::less<folly::StringPiece>>;
constexpr auto buddy = "buddy"_sp;
constexpr auto hello = "hello"_sp;
constexpr auto stake = "stake"_sp;
constexpr auto world = "world"_sp;
constexpr auto zebra = "zebra"_sp;
heap_vector_map<std::string, float, Compare> const m(
{{hello.str(), -1.0f}, {world.str(), +1.0f}});
// find
EXPECT_TRUE(m.end() == m.find(buddy));
EXPECT_EQ(hello, m.find(hello)->first);
EXPECT_TRUE(m.end() == m.find(stake));
EXPECT_EQ(world, m.find(world)->first);
EXPECT_TRUE(m.end() == m.find(zebra));
// count
EXPECT_EQ(0, m.count(buddy));
EXPECT_EQ(1, m.count(hello));
EXPECT_EQ(0, m.count(stake));
EXPECT_EQ(1, m.count(world));
EXPECT_EQ(0, m.count(zebra));
// lower_bound
EXPECT_TRUE(m.find(hello) == m.lower_bound(buddy));
EXPECT_TRUE(m.find(hello) == m.lower_bound(hello));
EXPECT_TRUE(m.find(world) == m.lower_bound(stake));
EXPECT_TRUE(m.find(world) == m.lower_bound(world));
EXPECT_TRUE(m.end() == m.lower_bound(zebra));
// upper_bound
EXPECT_TRUE(m.find(hello) == m.upper_bound(buddy));
EXPECT_TRUE(m.find(world) == m.upper_bound(hello));
EXPECT_TRUE(m.find(world) == m.upper_bound(stake));
EXPECT_TRUE(m.end() == m.upper_bound(world));
EXPECT_TRUE(m.end() == m.upper_bound(zebra));
// equal_range
for (auto value : {buddy, hello, stake, world, zebra}) {
EXPECT_TRUE(
std::make_pair(m.lower_bound(value), m.upper_bound(value)) ==
m.equal_range(value))
<< value;
}
}
TEST(HeapVectorTypes, Sizes) {
EXPECT_EQ(sizeof(heap_vector_set<int>), sizeof(std::vector<int>));
EXPECT_EQ(
sizeof(heap_vector_map<int, int>),
sizeof(std::vector<std::pair<int, int>>));
using SetT = heap_vector_set<
int,
std::less<int>,
std::allocator<int>,
OneAtATimePolicy>;
using MapT = heap_vector_map<
int,
int,
std::less<int>,
std::allocator<std::pair<int, int>>,
OneAtATimePolicy>;
EXPECT_EQ(sizeof(SetT), sizeof(std::vector<int>));
EXPECT_EQ(sizeof(MapT), sizeof(std::vector<std::pair<int, int>>));
}
TEST(HeapVectorTypes, Iterators) {
heap_vector_set<int> s;
heap_vector_map<int, int> m;
m[0] = 0;
m[1] = 1;
EXPECT_EQ(m.size(), 2);
EXPECT_EQ(
(char*)&*m.iterate().end() - (char*)&*m.iterate().begin(),
2 * sizeof(std::pair<int, int>));
heap_vector_set<int>::iterator setI = s.begin();
// verify iterator -> const_iterator works. Reverse produce compiler error.
heap_vector_set<int>::const_iterator csetI(setI);
heap_vector_map<int, int>::iterator mapI = m.begin();
heap_vector_map<int, int>::const_iterator cmapI(mapI);
}
TEST(HeapVectorTypes, InitializerLists) {
heap_vector_set<int> empty_initialized_set{};
EXPECT_TRUE(empty_initialized_set.empty());
heap_vector_set<int> singleton_initialized_set{1};
EXPECT_EQ(1, singleton_initialized_set.size());
EXPECT_EQ(1, *singleton_initialized_set.begin());
heap_vector_set<int> forward_initialized_set{1, 2};
heap_vector_set<int> backward_initialized_set{2, 1};
EXPECT_EQ(2, forward_initialized_set.size());
EXPECT_EQ(1, *forward_initialized_set.begin());
EXPECT_EQ(2, *forward_initialized_set.rbegin());
EXPECT_TRUE(forward_initialized_set == backward_initialized_set);
heap_vector_map<int, int> empty_initialized_map{};
EXPECT_TRUE(empty_initialized_map.empty());
heap_vector_map<int, int> singleton_initialized_map{{1, 10}};
EXPECT_EQ(1, singleton_initialized_map.size());
EXPECT_EQ(10, singleton_initialized_map[1]);
heap_vector_map<int, int> forward_initialized_map{{1, 10}, {2, 20}};
heap_vector_map<int, int> backward_initialized_map{{2, 20}, {1, 10}};
EXPECT_EQ(2, forward_initialized_map.size());
EXPECT_EQ(10, forward_initialized_map[1]);
EXPECT_EQ(20, forward_initialized_map[2]);
EXPECT_TRUE(forward_initialized_map == backward_initialized_map);
}
TEST(HeapVectorTypes, CustomCompare) {
heap_vector_set<int, less_invert<int>> s;
for (int i = 0; i < 200; ++i) {
s.insert(i);
}
check_invariant(s);
heap_vector_map<int, float, less_invert<int>> m;
for (int i = 0; i < 200; ++i) {
m[i] = 12.0f;
}
check_invariant(m);
}
TEST(HeapVectorTypes, GrowthPolicy) {
using SetT = heap_vector_set<
CountCopyCtor,
std::less<CountCopyCtor>,
std::allocator<CountCopyCtor>,
OneAtATimePolicy>;
SetT a;
for (int i = 0; i < 20; ++i) {
a.insert(CountCopyCtor(i));
}
check_invariant(a);
SetT::iterator it = a.begin();
EXPECT_FALSE(it == a.end());
if (it != a.end()) {
EXPECT_EQ(it->val_, 0);
// 1 copy for the initial insertion, 19 more for reallocs on the
// additional insertions.
EXPECT_EQ(it->count_, 20);
}
std::list<CountCopyCtor> v;
for (int i = 0; i < 20; ++i) {
v.emplace_back(20 + i);
}
a.insert(v.begin(), v.end());
check_invariant(a);
it = a.begin();
EXPECT_FALSE(it == a.end());
if (it != a.end()) {
EXPECT_EQ(it->val_, 0);
EXPECT_EQ(it->count_, 23);
}
}
TEST(HeapVectorTest, EmptyTest) {
heap_vector_set<int> emptySet;
EXPECT_TRUE(emptySet.lower_bound(10) == emptySet.end());
EXPECT_TRUE(emptySet.find(10) == emptySet.end());
heap_vector_map<int, int> emptyMap;
EXPECT_TRUE(emptyMap.lower_bound(10) == emptyMap.end());
EXPECT_TRUE(emptyMap.find(10) == emptyMap.end());
EXPECT_THROW(emptyMap.at(10), std::out_of_range);
}
TEST(HeapVectorTest, MoveTest) {
heap_vector_set<std::unique_ptr<int>> s;
s.insert(std::make_unique<int>(5));
s.insert(s.end(), std::make_unique<int>(10));
EXPECT_EQ(s.size(), 2);
for (const auto& p : s) {
EXPECT_TRUE(*p == 5 || *p == 10);
}
heap_vector_map<int, std::unique_ptr<int>> m;
m.insert(std::make_pair(5, std::make_unique<int>(5)));
m.insert(m.end(), std::make_pair(10, std::make_unique<int>(10)));
EXPECT_EQ(*m[5], 5);
EXPECT_EQ(*m[10], 10);
}
TEST(HeapVectorTest, ShrinkTest) {
heap_vector_map<int, int> s;
int i = 0;
// Hopefully your resize policy doubles when capacity is full, or this will
// hang forever :(
while (s.capacity() == s.size()) {
s.insert(std::make_pair(i++, i));
}
s.shrink_to_fit();
// The standard does not actually enforce that this be true, but assume that
// vector::shrink_to_fit respects the caller.
EXPECT_EQ(s.capacity(), s.size());
}
TEST(HeapVectorTypes, EraseTest) {
heap_vector_set<int> s;
for (int i = 0; i < 1000; ++i) {
s.insert(i);
}
auto it = s.lower_bound(32);
EXPECT_EQ(*it, 32);
it = s.erase(it);
EXPECT_NE(s.end(), it);
EXPECT_EQ(*it, 33);
it = s.erase(it, it + 5);
EXPECT_EQ(*it, 38);
it = s.begin();
while (it != s.end()) {
if (*it >= 5) {
it = s.erase(it);
} else {
it++;
}
}
EXPECT_EQ(it, s.end());
EXPECT_EQ(s.size(), 5);
heap_vector_map<int, int> m;
m.insert(std::make_pair(1, 1));
heap_vector_map<int, int> m2(m);
EXPECT_EQ(0, m.erase(0));
EXPECT_EQ(m2, m);
}
TEST(HeapVectorTypes, EraseTest2) {
heap_vector_set<int> s;
for (int i = 0; i < 1000; ++i) {
s.insert(i);
}
auto it = s.lower_bound(32);
EXPECT_EQ(*it, 32);
it = s.erase(it);
EXPECT_NE(s.end(), it);
EXPECT_EQ(*it, 33);
it = s.erase(it, it + 5);
EXPECT_EQ(*it, 38);
it = s.begin();
while (it != s.end()) {
if (*it >= 5) {
it = s.erase(it);
} else {
it++;
}
}
EXPECT_EQ(it, s.end());
EXPECT_EQ(s.size(), 5);
heap_vector_map<int, int> m;
for (int i = 0; i < 1000; ++i) {
m.insert(std::make_pair(i, i));
}
auto it2 = m.lower_bound(32);
EXPECT_EQ(it2->first, 32);
it2 = m.erase(it2);
EXPECT_NE(m.end(), it2);
EXPECT_EQ(it2->first, 33);
it2 = m.erase(it2, it2 + 5);
EXPECT_EQ(it2->first, 38);
it2 = m.begin();
while (it2 != m.end()) {
if (it2->first >= 5) {
it2 = m.erase(it2);
} else {
it2++;
}
}
EXPECT_EQ(it2, m.end());
EXPECT_EQ(m.size(), 5);
}
TEST(HeapVectorTypes, TestSetBulkInsertionSortMerge) {
auto s = std::vector<int>({6, 4, 8, 2});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
// Add an unsorted range that will have to be merged in.
s = std::vector<int>({10, 7, 5, 1});
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({1, 2, 4, 5, 6, 7, 8, 10}));
}
TEST(HeapVectorTypes, TestBulkInsertionUncopyableTypes) {
std::vector<std::pair<int, std::unique_ptr<int>>> s;
s.emplace_back(1, std::make_unique<int>(0));
heap_vector_map<int, std::unique_ptr<int>> vmap(
std::make_move_iterator(s.begin()), std::make_move_iterator(s.end()));
s.clear();
s.emplace_back(3, std::make_unique<int>(0));
vmap.insert(
std::make_move_iterator(s.begin()), std::make_move_iterator(s.end()));
}
TEST(HeapVectorTypes, TestSetBulkInsertionMiddleValuesEqualDuplication) {
auto s = std::vector<int>({4, 6, 8});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
s = std::vector<int>({8, 10, 12});
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({4, 6, 8, 10, 12}));
}
TEST(HeapVectorTypes, TestSetBulkInsertionSortMergeDups) {
auto s = std::vector<int>({6, 4, 8, 2});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
// Add an unsorted range that will have to be merged in.
s = std::vector<int>({10, 6, 5, 2});
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({2, 4, 5, 6, 8, 10}));
}
TEST(HeapVectorTypes, TestSetInsertionDupsOneByOne) {
auto s = std::vector<int>({6, 4, 8, 2});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
// Add an unsorted range that will have to be merged in.
s = std::vector<int>({10, 6, 5, 2});
for (const auto& elem : s) {
vset.insert(elem);
}
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({2, 4, 5, 6, 8, 10}));
}
TEST(HeapVectorTypes, TestSetBulkInsertionSortNoMerge) {
auto s = std::vector<int>({6, 4, 8, 2});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
// Add an unsorted range that will not have to be merged in.
s = std::vector<int>({20, 15, 16, 13});
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({2, 4, 6, 8, 13, 15, 16, 20}));
}
TEST(HeapVectorTypes, TestSetBulkInsertionNoSortMerge) {
auto s = std::vector<int>({6, 4, 8, 2});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
// Add a sorted range that will have to be merged in.
s = std::vector<int>({1, 3, 5, 9});
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({1, 2, 3, 4, 5, 6, 8, 9}));
}
TEST(HeapVectorTypes, TestSetBulkInsertionNoSortNoMerge) {
auto s = std::vector<int>({6, 4, 8, 2});
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
// Add a sorted range that will not have to be merged in.
s = std::vector<int>({21, 22, 23, 24});
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({2, 4, 6, 8, 21, 22, 23, 24}));
}
TEST(HeapVectorTypes, TestSetBulkInsertionEmptyRange) {
std::vector<int> s;
EXPECT_TRUE(s.empty());
// insertion of empty range into empty container.
heap_vector_set<int> vset(s.begin(), s.end());
check_invariant(vset);
s = std::vector<int>({6, 4, 8, 2});
vset.insert(s.begin(), s.end());
// insertion of empty range into non-empty container.
s.clear();
vset.insert(s.begin(), s.end());
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({2, 4, 6, 8}));
}
// A moveable and copyable struct, which we use to make sure that no copy
// operations are performed during bulk insertion if moving is an option.
struct Movable {
int x_;
explicit Movable(int x) : x_(x) {}
Movable(const Movable&) { ADD_FAILURE() << "Copy ctor should not be called"; }
Movable& operator=(const Movable&) {
ADD_FAILURE() << "Copy assignment should not be called";
return *this;
}
Movable(Movable&&) = default;
Movable& operator=(Movable&&) = default;
};
TEST(HeapVectorTypes, TestBulkInsertionMovableTypes) {
std::vector<std::pair<int, Movable>> s;
s.emplace_back(3, Movable(2));
s.emplace_back(1, Movable(0));
heap_vector_map<int, Movable> vmap(
std::make_move_iterator(s.begin()), std::make_move_iterator(s.end()));
s.clear();
s.emplace_back(4, Movable(3));
s.emplace_back(2, Movable(1));
vmap.insert(
std::make_move_iterator(s.begin()), std::make_move_iterator(s.end()));
}
TEST(HeapVectorTypes, TestSetCreationFromVector) {
std::vector<int> vec = {3, 1, -1, 5, 0};
heap_vector_set<int> vset(std::move(vec));
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({-1, 0, 1, 3, 5}));
}
TEST(HeapVectorTypes, TestMapCreationFromVector) {
std::vector<std::pair<int, int>> vec = {
{3, 1}, {1, 5}, {-1, 2}, {5, 3}, {0, 3}};
heap_vector_map<int, int> vmap(std::move(vec));
check_invariant(vmap);
auto contents = std::vector<std::pair<int, int>>(vmap.begin(), vmap.end());
auto expected_contents = std::vector<std::pair<int, int>>({
{-1, 2},
{0, 3},
{1, 5},
{3, 1},
{5, 3},
});
EXPECT_EQ(contents, expected_contents);
}
TEST(HeapVectorTypes, TestSetCreationFromSmallVector) {
using smvec = folly::small_vector<int, 5>;
smvec vec = {3, 1, -1, 5, 0};
heap_vector_set<
int,
std::less<int>,
std::allocator<std::pair<int, int>>,
void,
smvec>
vset(std::move(vec));
check_invariant(vset);
EXPECT_THAT(vset, testing::ElementsAreArray({-1, 0, 1, 3, 5}));
}
TEST(HeapVectorTypes, TestMapCreationFromSmallVector) {
using smvec = folly::small_vector<std::pair<int, int>, 5>;
smvec vec = {{3, 1}, {1, 5}, {-1, 2}, {5, 3}, {0, 3}};
heap_vector_map<
int,
int,
std::less<int>,
std::allocator<std::pair<int, int>>,
void,
smvec>
vmap(std::move(vec));
check_invariant(vmap);
auto contents = std::vector<std::pair<int, int>>(vmap.begin(), vmap.end());
auto expected_contents = std::vector<std::pair<int, int>>({
{-1, 2},
{0, 3},
{1, 5},
{3, 1},
{5, 3},
});
EXPECT_EQ(contents, expected_contents);
}
TEST(HeapVectorTypes, TestBulkInsertionWithDuplicatesIntoEmptySet) {
heap_vector_set<int> set;
{
std::vector<int> const vec = {0, 1, 0, 1};
set.insert(vec.begin(), vec.end());
}
EXPECT_THAT(set, testing::ElementsAreArray({0, 1}));
}
TEST(HeapVectorTypes, TestBulkInsertionWithDuplicatesIntoEmptyMap) {
std::vector<std::pair<int, int>> const vec = {{0, 0}, {1, 1}, {0, 2}, {1, 3}};
heap_vector_map<int, int> m(vec.begin(), vec.end());
EXPECT_EQ(m.size(), 2);
EXPECT_EQ(m[0], 0);
EXPECT_EQ(m[1], 1);
heap_vector_map<int, int> m2;
m2[2] = 2;
m2[-1] = -1;
// merge two heap maps.
m2.insert(
std::make_move_iterator(m.iterate().begin()),
std::make_move_iterator(m.iterate().end()));
EXPECT_EQ(m2.size(), 4);
EXPECT_EQ(m2[0], 0);
EXPECT_EQ(m2[1], 1);
}
TEST(HeapVectorTypes, TestDataPointsToFirstElement) {
heap_vector_map<int, int> map;
map[0] = 0;
// works if map has a single element. otherwise data points to middle element
EXPECT_EQ(&*map.iterate().data(), &*map.begin());
map[1] = 1;
// data() does not point to begin()!
// A major difference between heap_vector_map and sorted_vector_map.
EXPECT_NE(&*map.iterate().data(), &*map.begin());
}
TEST(HeapVectorTypes, TestEmplaceHint) {
heap_vector_map<int, int> map;
for (size_t i = 0; i < 4; ++i) {
const std::pair<int, int> k00(0, 0);
const std::pair<int, int> k10(1, 0);
const std::pair<int, int> k1i(1, i % 2);
const std::pair<int, int> k20(2, 0);
const std::pair<int, int> k2i(2, i % 2);
EXPECT_EQ(*map.emplace_hint(map.begin(), 0, i % 2), k00);
EXPECT_EQ(*map.emplace_hint(map.begin(), k1i), k10);
EXPECT_EQ(*map.emplace_hint(map.begin(), folly::copy(k2i)), k20);
check_invariant(map);
}
}
TEST(HeapVectorTypes, TestExceptionSafety) {
std::initializer_list<std::pair<KeyThatThrowsOnCopies, int>> const
sortedUnique = {
{0, 0}, {1, 1}, {4, 4}, {7, 7}, {9, 9}, {11, 11}, {15, 15}};
heap_vector_map<KeyThatThrowsOnCopies, int> map = {sortedUnique};
EXPECT_EQ(map.size(), 7);
// Verify that we successfully insert when no exceptions are thrown.
KeyThatThrowsOnCopies key1(96, false);
auto hint1 = map.find(96);
map.emplace_hint(hint1, key1, 96);
EXPECT_EQ(map.size(), 8);
// Verify that we don't add a key at the end if copying throws
KeyThatThrowsOnCopies key2(99, true);
auto hint2 = map.find(99);
try {
map.emplace_hint(hint2, key2, 99);
} catch (const KeyCopiedException&) {
// swallow
}
EXPECT_EQ(map.size(), 8);
// Verify that we don't add a key in the middle if copying throws
KeyThatThrowsOnCopies key3(47, true);
auto hint3 = map.find(47);
try {
map.emplace_hint(hint3, key3, 47);
} catch (const KeyCopiedException&) {
// swallow
}
EXPECT_EQ(map.size(), 8);
}
#if FOLLY_HAS_MEMORY_RESOURCE
using folly::detail::std_pmr::memory_resource;
using folly::detail::std_pmr::new_delete_resource;
using folly::detail::std_pmr::null_memory_resource;
using folly::detail::std_pmr::polymorphic_allocator;
namespace {
struct test_resource : public memory_resource {
void* do_allocate(size_t bytes, size_t /* alignment */) override {
return folly::checkedMalloc(bytes);
}
void do_deallocate(
void* p, size_t /* bytes */, size_t /* alignment */) noexcept override {
free(p);
}
bool do_is_equal(const memory_resource& other) const noexcept override {
return this == &other;
}
};
} // namespace
TEST(HeapVectorTypes, TestPmrAllocatorSimple) {
namespace pmr = folly::pmr;
pmr::heap_vector_set<std::pair<int, int>> s(null_memory_resource());
EXPECT_THROW(s.emplace(42, 42), std::bad_alloc);
pmr::heap_vector_map<int, int> m(null_memory_resource());
EXPECT_THROW(m.emplace(42, 42), std::bad_alloc);
}
TEST(HeapVectorTypes, TestPmrCopyConstructSameAlloc) {
namespace pmr = folly::pmr;
set_default_resource(null_memory_resource());
test_resource r;
polymorphic_allocator<std::byte> a1(&r), a2(&r);
EXPECT_EQ(a1, a2);
{
pmr::heap_vector_set<int> s1(a1);
s1.emplace(42);
pmr::heap_vector_set<int> s2(s1, a2);
EXPECT_EQ(s1.get_allocator(), s2.get_allocator());
EXPECT_EQ(s2.count(42), 1);
}
{
pmr::heap_vector_map<int, int> m1(a1);
m1.emplace(42, 42);
pmr::heap_vector_map<int, int> m2(m1, a2);
EXPECT_EQ(m1.get_allocator(), m2.get_allocator());
EXPECT_EQ(m2.at(42), 42);
}
}
TEST(HeapVectorTypes, TestPmrCopyConstructDifferentAlloc) {
namespace pmr = folly::pmr;
set_default_resource(null_memory_resource());
test_resource r1, r2;
polymorphic_allocator<std::byte> a1(&r1), a2(&r2);
EXPECT_NE(a1, a2);
{
pmr::heap_vector_set<int> s1(a1);
s1.emplace(42);
pmr::heap_vector_set<int> s2(s1, a2);
EXPECT_NE(s1.get_allocator(), s2.get_allocator());
EXPECT_EQ(s2.count(42), 1);
}
{
pmr::heap_vector_map<int, int> m1(a1);
m1.emplace(42, 42);
pmr::heap_vector_map<int, int> m2(m1, a2);
EXPECT_NE(m1.get_allocator(), m2.get_allocator());
EXPECT_EQ(m2.at(42), 42);
}
}
TEST(HeapVectorTypes, TestPmrMoveConstructSameAlloc) {
namespace pmr = folly::pmr;
set_default_resource(null_memory_resource());
test_resource r;
polymorphic_allocator<std::byte> a1(&r), a2(&r);
EXPECT_EQ(a1, a2);
{
pmr::heap_vector_set<int> s1(a1);
s1.emplace(42);
auto d = s1.iterate().data();
pmr::heap_vector_set<int> s2(std::move(s1), a2);
EXPECT_EQ(s1.get_allocator(), s2.get_allocator());
EXPECT_EQ(s2.iterate().data(), d);
EXPECT_EQ(s2.count(42), 1);
}
{
pmr::heap_vector_map<int, int> m1(a1);
m1.emplace(42, 42);
auto d = m1.iterate().data();
pmr::heap_vector_map<int, int> m2(std::move(m1), a2);
EXPECT_EQ(m1.get_allocator(), m2.get_allocator());
EXPECT_EQ(m2.iterate().data(), d);
EXPECT_EQ(m2.at(42), 42);
}
}
TEST(HeapVectorTypes, TestPmrMoveConstructDifferentAlloc) {
namespace pmr = folly::pmr;
set_default_resource(null_memory_resource());
test_resource r1, r2;
polymorphic_allocator<std::byte> a1(&r1), a2(&r2);
EXPECT_NE(a1, a2);
{
pmr::heap_vector_set<int> s1(a1);
s1.emplace(42);
auto d = s1.iterate().data();
pmr::heap_vector_set<int> s2(std::move(s1), a2);
EXPECT_NE(s1.get_allocator(), s2.get_allocator());
EXPECT_NE(s2.iterate().data(), d);
EXPECT_EQ(s2.count(42), 1);
}
{
pmr::heap_vector_map<int, int> m1(a1);
m1.emplace(42, 42);
auto d = m1.iterate().data();
pmr::heap_vector_map<int, int> m2(std::move(m1), a2);
EXPECT_NE(m1.get_allocator(), m2.get_allocator());
EXPECT_NE(m2.iterate().data(), d);
EXPECT_EQ(m2.at(42), 42);
}
}
template <typename T>
using pmr_vector =
std::vector<T, folly::detail::std_pmr::polymorphic_allocator<T>>;
TEST(HeapVectorTypes, TestCreationFromPmrVector) {
namespace pmr = folly::pmr;
set_default_resource(null_memory_resource());
test_resource r;
polymorphic_allocator<std::byte> a(&r);
{
pmr_vector<int> c({1, 2, 3}, a);
auto d = c.data();
pmr::heap_vector_set<int> s(std::move(c));
EXPECT_EQ(s.get_allocator(), a);
EXPECT_EQ(&*s.iterate().data(), d);
}
{
pmr_vector<int> c({2, 1, 3}, a);
auto d = c.data();
pmr::heap_vector_set<int> s(std::move(c));
EXPECT_EQ(s.get_allocator(), a);
EXPECT_EQ(&*s.iterate().data(), d);
}
{
pmr_vector<std::pair<int, int>> c({{1, 1}, {2, 2}, {3, 3}}, a);
auto d = c.data();
pmr::heap_vector_map<int, int> m(std::move(c));
EXPECT_EQ(m.get_allocator(), a);
EXPECT_EQ(&*m.iterate().data(), d);
}
{
pmr_vector<std::pair<int, int>> c({{2, 2}, {1, 1}, {3, 3}}, a);
auto d = c.data();
pmr::heap_vector_map<int, int> m(std::move(c));
EXPECT_EQ(m.get_allocator(), a);
EXPECT_EQ(&*m.iterate().data(), d);
}
}
TEST(HeapVectorTypes, TestPmrAllocatorScoped) {
namespace pmr = folly::pmr;
set_default_resource(null_memory_resource());
polymorphic_allocator<std::byte> alloc(new_delete_resource());
{
pmr::heap_vector_set<pmr_vector<int>> s(alloc);
s.emplace(1);
EXPECT_EQ(s.begin()->get_allocator(), alloc);
}
{
pmr::heap_vector_set<pmr_vector<int>> s(alloc);
s.emplace_hint(s.begin(), 1);
EXPECT_EQ(s.begin()->get_allocator(), alloc);
}
{
pmr::heap_vector_map<int, pmr_vector<int>> m(alloc);
m.emplace(1, 1);
EXPECT_EQ(m.begin()->second.get_allocator(), alloc);
}
{
pmr::heap_vector_map<int, pmr_vector<int>> m(alloc);
m.emplace_hint(m.begin(), 1, 1);
EXPECT_EQ(m.begin()->second.get_allocator(), alloc);
}
{
pmr::heap_vector_map<int, pmr::heap_vector_map<int, int>> m(alloc);
m.emplace(
std::piecewise_construct,
std::forward_as_tuple(42),
std::forward_as_tuple(
std::initializer_list<std::pair<int, int>>{{42, 42}}));
EXPECT_EQ(m.begin()->second.get_allocator(), alloc);
}
{
pmr::heap_vector_map<int, pmr::heap_vector_map<int, int>> m(alloc);
m.emplace_hint(
m.begin(),
std::piecewise_construct,
std::forward_as_tuple(42),
std::forward_as_tuple(
std::initializer_list<std::pair<int, int>>{{42, 42}}));
EXPECT_EQ(m.begin()->second.get_allocator(), alloc);
}
{
pmr::heap_vector_map<int, pmr::heap_vector_map<int, int>> m(alloc);
m[42][42] = 42;
EXPECT_EQ(m.begin()->second.get_allocator(), alloc);
}
}
#endif
TEST(HeapVectorTypes, TestInsertHintCopy) {
heap_vector_set<CountCopyCtor> set;
heap_vector_map<CountCopyCtor, int> map;
CountCopyCtor skey;
std::pair<CountCopyCtor, int> mkey;
CountCopyCtor::gCount_ = 0;
set.insert(set.end(), skey);
map.insert(map.end(), mkey);
EXPECT_EQ(CountCopyCtor::gCount_, 2);
set.emplace(CountCopyCtor(1));
map.emplace(CountCopyCtor(1), 1);
CountCopyCtor::gCount_ = 0;
for (size_t i = 0; i <= map.size(); ++i) {
auto sit = set.begin();
auto mit = map.begin();
std::advance(sit, i);
std::advance(mit, i);
set.insert(sit, skey);
map.insert(mit, mkey);
}
EXPECT_EQ(CountCopyCtor::gCount_, 0);
}
TEST(HeapVectorTypes, TestIterator) {
heap_vector_map<int, int> m;
const int size = 11;
for (int i = 0; i < size; i++) {
m[i] = i;
}
// Test C++ generics, idioms
// distance
EXPECT_EQ(std::distance(++m.begin(), m.end()), 10);
EXPECT_EQ(std::distance(m.begin(), --m.end()), 10);
EXPECT_EQ(std::distance(m.end(), m.begin()), -11);
EXPECT_EQ(std::distance(--m.end(), m.begin()), -10);
// std::copy
std::vector<std::pair<int, int>> v;
v.resize(size);
std::copy(m.begin(), m.end(), v.begin());
for (int i = 0; i < size; i++) {
EXPECT_EQ(v[i].first, i);
}
// rbegin
auto i = size;
for (auto I = m.rbegin(); I != m.rend(); ++I) {
EXPECT_EQ(I->first, --i);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment