cai_lw's competitive programming library
 
Loading...
Searching...
No Matches
table.hpp
1#include <cassert>
2#include <iterator>
3#include <optional>
4#include <variant>
5
6#include "cplib/hash/wyhash.hpp"
7
8namespace cplib {
9
10namespace impl {
11
12template <typename T>
13struct HashCell {
14 enum State { EMPTY, PHANTOM, SENTINEL };
15
16 std::variant<State, T> inner;
17
18 HashCell() : inner(State::EMPTY) {}
19
20 bool empty() const { return inner.index() == 0 && std::get<0>(inner) == State::EMPTY; }
21
22 bool occupied() const { return inner.index() == 1; }
23
24 bool phantom() const { return inner.index() == 0 && std::get<0>(inner) == State::PHANTOM; }
25
26 bool sentinel() const { return inner.index() == 0 && std::get<0>(inner) == State::SENTINEL; }
27
28 void insert(const T& val) { inner = val; }
29
30 void insert(T&& val) { inner = std::move(val); }
31
32 const T& value() const { return std::get<1>(inner); }
33
34 T& value() { return std::get<1>(inner); }
35
36 void erase() { inner = State::PHANTOM; }
37};
38
39} // namespace impl
40
61template <typename T, typename Hash = WyHash<T>, typename Eq = std::equal_to<T>>
62class HashTable {
63 public:
64 using size_type = std::size_t;
65 using value_type = T;
66 using cell_iterator = typename std::vector<impl::HashCell<T>>::iterator;
67
73 HashTable() : nonempty(0), occupied(0), disable_shrink(false) { _allocate_cells(4); }
74
81 template <typename InputIt>
82 HashTable(InputIt first, InputIt last) : nonempty(0), occupied(0), disable_shrink(false) {
83 size_type cap = 4;
84 if (std::is_base_of_v<std::random_access_iterator_tag, std::iterator_traits<InputIt>::iterator_category>) {
85 auto dist = std::distance(first, last);
86 while (dist > (cap >> 1)) {
87 cap <<= 1;
88 }
89 }
90 _allocate_cells(cap);
91 for (; first != last; ++first) {
92 insert(*first);
93 }
94 }
95
97 size_type size() const { return occupied; }
98
100 bool empty() const { return occupied == 0; }
101
107 size_type capacity() const { return cap_mask + 1; }
108
110 cell_iterator cell_begin() { return cells.begin(); }
111
118 cell_iterator cell_end() { return --cells.end(); }
119
126 template <bool Revive = false>
127 cell_iterator find_cell(const T& x) {
128 size_type loc = hash(x) & cap_mask;
129 std::optional<cell_iterator> phantom;
130 while (!cells[loc].empty()) {
131 if (cells[loc].occupied() && eq(x, cells[loc].value())) {
132 return cells.begin() + loc;
133 }
134 if (Revive && cells[loc].phantom()) {
135 phantom = cells.begin() + loc;
136 }
137 loc = (loc + 1) & cap_mask;
138 }
139 if (Revive && phantom) {
140 return *phantom;
141 } else {
142 return cells.begin() + loc;
143 }
144 }
145
147 bool contains(const T& x) const { return !const_cast<HashTable*>(this)->find_cell(x)->empty(); }
148
155 template <bool Replace = false>
156 bool insert(const T& x) {
157 cell_iterator cell = find_cell<true>(x);
158 bool do_insert = true;
159 if (!cell->occupied()) {
160 ++occupied;
161 if (cell->empty()) {
162 ++nonempty;
163 }
164 cell->insert(x);
165 _check_rehash();
166 } else if (Replace) {
167 cell->insert(x);
168 } else {
169 do_insert = false;
170 }
171 return do_insert;
172 }
173
175 template <bool Replace = false>
176 bool insert(T&& x) {
177 cell_iterator cell = find_cell<true>(x);
178 bool do_insert = true;
179 if (!cell->occupied()) {
180 ++occupied;
181 if (cell->empty()) {
182 ++nonempty;
183 }
184 cell->insert(std::move(x));
185 _check_rehash();
186 } else if (Replace) {
187 cell->insert(std::move(x));
188 } else {
189 do_insert = false;
190 }
191 return do_insert;
192 }
193
195 bool erase(const T& x) {
196 cell_iterator cell = find_cell(x);
197 bool do_erase = !cell->empty();
198 if (do_erase) {
199 cell->erase();
200 --occupied;
201 _check_rehash();
202 }
203 return do_erase;
204 }
205
211 void rehash(size_type new_cap) {
212 assert(new_cap >= 4 && (new_cap & (new_cap - 1)) == 0);
213 std::vector<impl::HashCell<T>> old_cells = std::move(cells);
214 size_type old_cap = cap_mask + 1;
215 _allocate_cells(new_cap);
216 nonempty = occupied;
217 for (size_type i = 0; i < old_cap; ++i) {
218 if (old_cells[i].occupied()) {
219 size_type loc = hash(old_cells[i].value()) & cap_mask;
220 while (!cells[loc].empty()) {
221 loc = (loc + 1) & cap_mask;
222 }
223 cells[loc] = std::move(old_cells[i]);
224 }
225 }
226 }
227
242 void reserve(size_type new_size) {
243 size_type new_cap = capacity();
244 while (new_size > (new_cap >> 1)) {
245 new_cap <<= 1;
246 }
247 if (new_cap > capacity()) {
248 rehash(new_cap);
249 disable_shrink = true;
250 }
251 }
252
253 private:
254 std::vector<impl::HashCell<T>> cells;
255 size_type nonempty, occupied, cap_mask;
256 bool disable_shrink;
257 Hash hash;
258 Eq eq;
259
260 // Allocate a new array of cells, set sentinel and cap_mask.
261 void _allocate_cells(size_type cap) {
262 cells = std::vector<impl::HashCell<T>>(cap + 1);
263 cells.back().inner = impl::HashCell<T>::State::SENTINEL;
264 cap_mask = cap - 1;
265 }
266
267 // See class documentation for rehashing policy.
268 void _check_rehash() {
269 size_type cap = capacity();
270 if (!disable_shrink && occupied < (cap >> 3)) {
271 do {
272 cap >>= 1;
273 } while (occupied < (cap >> 3));
274 rehash(cap);
275 } else if (nonempty > (cap >> 1)) {
276 disable_shrink = false;
277 if (occupied > (cap >> 2)) {
278 cap <<= 1;
279 }
280 rehash(cap);
281 }
282 }
283};
284
285} // namespace cplib
Linear probing hash table with std non-compliant interface.
Definition: table.hpp:62
bool insert(T &&x)
Insert an element if there is no element already present that compares equal to it.
Definition: table.hpp:176
cell_iterator cell_begin()
Returns pointer to the beginning of the cell array.
Definition: table.hpp:110
size_type capacity() const
Returns the size of the underlying array of cells.
Definition: table.hpp:107
bool insert(const T &x)
Insert an element if there is no element already present that compares equal to it.
Definition: table.hpp:156
bool erase(const T &x)
Remove the element that compares equal to the given value if it is present.
Definition: table.hpp:195
cell_iterator find_cell(const T &x)
Find an element that compares equal to the given value, or an empty cell if not found.
Definition: table.hpp:127
bool contains(const T &x) const
Returns whether an element that compares equal to the given value is found in the hash table.
Definition: table.hpp:147
bool empty() const
Returns whether the hash table contains no element.
Definition: table.hpp:100
HashTable()
Constructs an empty hash table.
Definition: table.hpp:73
HashTable(InputIt first, InputIt last)
Constructs a hash table containing elements from a pair of iterators.
Definition: table.hpp:82
void rehash(size_type new_cap)
Allocate the given capacity, and rehash all elements.
Definition: table.hpp:211
void reserve(size_type new_size)
Reserve capacity and temporary disable shrinking.
Definition: table.hpp:242
size_type size() const
Returns the number of elements in the hash table.
Definition: table.hpp:97
cell_iterator cell_end()
Returns pointer to the end of the cell array.
Definition: table.hpp:118