1 | /** |
2 | * MIT License |
3 | * |
4 | * Copyright (c) 2017 Tessil |
5 | * |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | * of this software and associated documentation files (the "Software"), to deal |
8 | * in the Software without restriction, including without limitation the rights |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
10 | * copies of the Software, and to permit persons to whom the Software is |
11 | * furnished to do so, subject to the following conditions: |
12 | * |
13 | * The above copyright notice and this permission notice shall be included in all |
14 | * copies or substantial portions of the Software. |
15 | * |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
22 | * SOFTWARE. |
23 | */ |
24 | #ifndef TSL_HOPSCOTCH_SET_H |
25 | #define TSL_HOPSCOTCH_SET_H |
26 | |
27 | |
28 | #include <algorithm> |
29 | #include <cstddef> |
30 | #include <functional> |
31 | #include <initializer_list> |
32 | #include <list> |
33 | #include <memory> |
34 | #include <type_traits> |
35 | #include <utility> |
36 | #include "hopscotch_hash.h" |
37 | |
38 | |
39 | namespace tsl { |
40 | |
41 | /** |
42 | * Implementation of a hash set using the hopscotch hashing algorithm. |
43 | * |
44 | * The Key must be either nothrow move-constructible, copy-constuctible or both. |
45 | * |
46 | * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false. |
47 | * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting |
48 | * the NeighborhoodSize to <= 30. There is no memory usage difference between |
49 | * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'. |
50 | * |
51 | * Storing the hash may improve performance on insert during the rehash process if the hash takes time |
52 | * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss). |
53 | * If used with simple Hash and KeyEqual it may slow things down. |
54 | * |
55 | * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy. |
56 | * |
57 | * GrowthPolicy defines how the set grows and consequently how a hash value is mapped to a bucket. |
58 | * By default the set uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets |
59 | * to a power of two and uses a mask to set the hash to a bucket instead of the slow modulo. |
60 | * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface. |
61 | * |
62 | * If the destructor of Key throws an exception, behaviour of the class is undefined. |
63 | * |
64 | * Iterators invalidation: |
65 | * - clear, operator=, reserve, rehash: always invalidate the iterators. |
66 | * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators |
67 | * if a displacement is needed to resolve a collision (which mean that most of the time, |
68 | * insert will invalidate the iterators). Or if there is a rehash. |
69 | * - erase: iterator on the erased element is the only one which become invalid. |
70 | */ |
71 | template<class Key, |
72 | class Hash = std::hash<Key>, |
73 | class KeyEqual = std::equal_to<Key>, |
74 | class Allocator = std::allocator<Key>, |
75 | unsigned int NeighborhoodSize = 62, |
76 | bool StoreHash = false, |
77 | class GrowthPolicy = tsl::power_of_two_growth_policy> |
78 | class hopscotch_set { |
79 | private: |
80 | template<typename U> |
81 | using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent<U>; |
82 | |
83 | class KeySelect { |
84 | public: |
85 | using key_type = Key; |
86 | |
87 | const key_type& operator()(const Key& key) const { |
88 | return key; |
89 | } |
90 | |
91 | key_type& operator()(Key& key) { |
92 | return key; |
93 | } |
94 | }; |
95 | |
96 | |
97 | using overflow_container_type = std::list<Key, Allocator>; |
98 | using ht = detail_hopscotch_hash::hopscotch_hash<Key, KeySelect, void, |
99 | Hash, KeyEqual, |
100 | Allocator, NeighborhoodSize, |
101 | StoreHash, GrowthPolicy, |
102 | overflow_container_type>; |
103 | |
104 | public: |
105 | using key_type = typename ht::key_type; |
106 | using value_type = typename ht::value_type; |
107 | using size_type = typename ht::size_type; |
108 | using difference_type = typename ht::difference_type; |
109 | using hasher = typename ht::hasher; |
110 | using key_equal = typename ht::key_equal; |
111 | using allocator_type = typename ht::allocator_type; |
112 | using reference = typename ht::reference; |
113 | using const_reference = typename ht::const_reference; |
114 | using pointer = typename ht::pointer; |
115 | using const_pointer = typename ht::const_pointer; |
116 | using iterator = typename ht::iterator; |
117 | using const_iterator = typename ht::const_iterator; |
118 | |
119 | |
120 | /* |
121 | * Constructors |
122 | */ |
123 | hopscotch_set() : hopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { |
124 | } |
125 | |
126 | explicit hopscotch_set(size_type bucket_count, |
127 | const Hash& hash = Hash(), |
128 | const KeyEqual& equal = KeyEqual(), |
129 | const Allocator& alloc = Allocator()) : |
130 | m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) |
131 | { |
132 | } |
133 | |
134 | hopscotch_set(size_type bucket_count, |
135 | const Allocator& alloc) : hopscotch_set(bucket_count, Hash(), KeyEqual(), alloc) |
136 | { |
137 | } |
138 | |
139 | hopscotch_set(size_type bucket_count, |
140 | const Hash& hash, |
141 | const Allocator& alloc) : hopscotch_set(bucket_count, hash, KeyEqual(), alloc) |
142 | { |
143 | } |
144 | |
145 | explicit hopscotch_set(const Allocator& alloc) : hopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { |
146 | } |
147 | |
148 | template<class InputIt> |
149 | hopscotch_set(InputIt first, InputIt last, |
150 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, |
151 | const Hash& hash = Hash(), |
152 | const KeyEqual& equal = KeyEqual(), |
153 | const Allocator& alloc = Allocator()) : hopscotch_set(bucket_count, hash, equal, alloc) |
154 | { |
155 | insert(first, last); |
156 | } |
157 | |
158 | template<class InputIt> |
159 | hopscotch_set(InputIt first, InputIt last, |
160 | size_type bucket_count, |
161 | const Allocator& alloc) : hopscotch_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) |
162 | { |
163 | } |
164 | |
165 | template<class InputIt> |
166 | hopscotch_set(InputIt first, InputIt last, |
167 | size_type bucket_count, |
168 | const Hash& hash, |
169 | const Allocator& alloc) : hopscotch_set(first, last, bucket_count, hash, KeyEqual(), alloc) |
170 | { |
171 | } |
172 | |
173 | hopscotch_set(std::initializer_list<value_type> init, |
174 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, |
175 | const Hash& hash = Hash(), |
176 | const KeyEqual& equal = KeyEqual(), |
177 | const Allocator& alloc = Allocator()) : |
178 | hopscotch_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) |
179 | { |
180 | } |
181 | |
182 | hopscotch_set(std::initializer_list<value_type> init, |
183 | size_type bucket_count, |
184 | const Allocator& alloc) : |
185 | hopscotch_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) |
186 | { |
187 | } |
188 | |
189 | hopscotch_set(std::initializer_list<value_type> init, |
190 | size_type bucket_count, |
191 | const Hash& hash, |
192 | const Allocator& alloc) : |
193 | hopscotch_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) |
194 | { |
195 | } |
196 | |
197 | |
198 | hopscotch_set& operator=(std::initializer_list<value_type> ilist) { |
199 | m_ht.clear(); |
200 | |
201 | m_ht.reserve(ilist.size()); |
202 | m_ht.insert(ilist.begin(), ilist.end()); |
203 | |
204 | return *this; |
205 | } |
206 | |
207 | allocator_type get_allocator() const { return m_ht.get_allocator(); } |
208 | |
209 | |
210 | /* |
211 | * Iterators |
212 | */ |
213 | iterator begin() noexcept { return m_ht.begin(); } |
214 | const_iterator begin() const noexcept { return m_ht.begin(); } |
215 | const_iterator cbegin() const noexcept { return m_ht.cbegin(); } |
216 | |
217 | iterator end() noexcept { return m_ht.end(); } |
218 | const_iterator end() const noexcept { return m_ht.end(); } |
219 | const_iterator cend() const noexcept { return m_ht.cend(); } |
220 | |
221 | |
222 | /* |
223 | * Capacity |
224 | */ |
225 | bool empty() const noexcept { return m_ht.empty(); } |
226 | size_type size() const noexcept { return m_ht.size(); } |
227 | size_type max_size() const noexcept { return m_ht.max_size(); } |
228 | |
229 | /* |
230 | * Modifiers |
231 | */ |
232 | void clear() noexcept { m_ht.clear(); } |
233 | |
234 | |
235 | |
236 | |
237 | std::pair<iterator, bool> insert(const value_type& value) { return m_ht.insert(value); } |
238 | std::pair<iterator, bool> insert(value_type&& value) { return m_ht.insert(std::move(value)); } |
239 | |
240 | iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } |
241 | iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } |
242 | |
243 | template<class InputIt> |
244 | void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } |
245 | void insert(std::initializer_list<value_type> ilist) { m_ht.insert(ilist.begin(), ilist.end()); } |
246 | |
247 | |
248 | |
249 | |
250 | /** |
251 | * Due to the way elements are stored, emplace will need to move or copy the key-value once. |
252 | * The method is equivalent to insert(value_type(std::forward<Args>(args)...)); |
253 | * |
254 | * Mainly here for compatibility with the std::unordered_map interface. |
255 | */ |
256 | template<class... Args> |
257 | std::pair<iterator, bool> emplace(Args&&... args) { return m_ht.emplace(std::forward<Args>(args)...); } |
258 | |
259 | |
260 | |
261 | |
262 | /** |
263 | * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. |
264 | * The method is equivalent to insert(hint, value_type(std::forward<Args>(args)...)); |
265 | * |
266 | * Mainly here for compatibility with the std::unordered_map interface. |
267 | */ |
268 | template<class... Args> |
269 | iterator emplace_hint(const_iterator hint, Args&&... args) { |
270 | return m_ht.emplace_hint(hint, std::forward<Args>(args)...); |
271 | } |
272 | |
273 | |
274 | |
275 | |
276 | iterator erase(iterator pos) { return m_ht.erase(pos); } |
277 | iterator erase(const_iterator pos) { return m_ht.erase(pos); } |
278 | iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } |
279 | size_type erase(const key_type& key) { return m_ht.erase(key); } |
280 | |
281 | /** |
282 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
283 | * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. |
284 | */ |
285 | size_type erase(const key_type& key, std::size_t precalculated_hash) { |
286 | return m_ht.erase(key, precalculated_hash); |
287 | } |
288 | |
289 | /** |
290 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. |
291 | * If so, K must be hashable and comparable to Key. |
292 | */ |
293 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
294 | size_type erase(const K& key) { return m_ht.erase(key); } |
295 | |
296 | /** |
297 | * @copydoc erase(const K& key) |
298 | * |
299 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
300 | * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. |
301 | */ |
302 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
303 | size_type erase(const K& key, std::size_t precalculated_hash) { |
304 | return m_ht.erase(key, precalculated_hash); |
305 | } |
306 | |
307 | |
308 | |
309 | |
310 | void swap(hopscotch_set& other) { other.m_ht.swap(m_ht); } |
311 | |
312 | |
313 | /* |
314 | * Lookup |
315 | */ |
316 | size_type count(const Key& key) const { return m_ht.count(key); } |
317 | |
318 | /** |
319 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
320 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
321 | */ |
322 | size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } |
323 | |
324 | /** |
325 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. |
326 | * If so, K must be hashable and comparable to Key. |
327 | */ |
328 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
329 | size_type count(const K& key) const { return m_ht.count(key); } |
330 | |
331 | /** |
332 | * @copydoc count(const K& key) const |
333 | * |
334 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
335 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
336 | */ |
337 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
338 | size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } |
339 | |
340 | |
341 | |
342 | |
343 | iterator find(const Key& key) { return m_ht.find(key); } |
344 | |
345 | /** |
346 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
347 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
348 | */ |
349 | iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } |
350 | |
351 | const_iterator find(const Key& key) const { return m_ht.find(key); } |
352 | |
353 | /** |
354 | * @copydoc find(const Key& key, std::size_t precalculated_hash) |
355 | */ |
356 | const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } |
357 | |
358 | /** |
359 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. |
360 | * If so, K must be hashable and comparable to Key. |
361 | */ |
362 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
363 | iterator find(const K& key) { return m_ht.find(key); } |
364 | |
365 | /** |
366 | * @copydoc find(const K& key) |
367 | * |
368 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
369 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
370 | */ |
371 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
372 | iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } |
373 | |
374 | /** |
375 | * @copydoc find(const K& key) |
376 | */ |
377 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
378 | const_iterator find(const K& key) const { return m_ht.find(key); } |
379 | |
380 | /** |
381 | * @copydoc find(const K& key) |
382 | * |
383 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
384 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
385 | */ |
386 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
387 | const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } |
388 | |
389 | |
390 | |
391 | |
392 | std::pair<iterator, iterator> equal_range(const Key& key) { return m_ht.equal_range(key); } |
393 | |
394 | /** |
395 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
396 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
397 | */ |
398 | std::pair<iterator, iterator> equal_range(const Key& key, std::size_t precalculated_hash) { |
399 | return m_ht.equal_range(key, precalculated_hash); |
400 | } |
401 | |
402 | std::pair<const_iterator, const_iterator> equal_range(const Key& key) const { return m_ht.equal_range(key); } |
403 | |
404 | /** |
405 | * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) |
406 | */ |
407 | std::pair<const_iterator, const_iterator> equal_range(const Key& key, std::size_t precalculated_hash) const { |
408 | return m_ht.equal_range(key, precalculated_hash); |
409 | } |
410 | |
411 | /** |
412 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. |
413 | * If so, K must be hashable and comparable to Key. |
414 | */ |
415 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
416 | std::pair<iterator, iterator> equal_range(const K& key) { return m_ht.equal_range(key); } |
417 | |
418 | /** |
419 | * @copydoc equal_range(const K& key) |
420 | * |
421 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same |
422 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. |
423 | */ |
424 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
425 | std::pair<iterator, iterator> equal_range(const K& key, std::size_t precalculated_hash) { |
426 | return m_ht.equal_range(key, precalculated_hash); |
427 | } |
428 | |
429 | /** |
430 | * @copydoc equal_range(const K& key) |
431 | */ |
432 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
433 | std::pair<const_iterator, const_iterator> equal_range(const K& key) const { return m_ht.equal_range(key); } |
434 | |
435 | /** |
436 | * @copydoc equal_range(const K& key, std::size_t precalculated_hash) |
437 | */ |
438 | template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr> |
439 | std::pair<const_iterator, const_iterator> equal_range(const K& key, std::size_t precalculated_hash) const { |
440 | return m_ht.equal_range(key, precalculated_hash); |
441 | } |
442 | |
443 | |
444 | |
445 | |
446 | /* |
447 | * Bucket interface |
448 | */ |
449 | size_type bucket_count() const { return m_ht.bucket_count(); } |
450 | size_type max_bucket_count() const { return m_ht.max_bucket_count(); } |
451 | |
452 | |
453 | /* |
454 | * Hash policy |
455 | */ |
456 | float load_factor() const { return m_ht.load_factor(); } |
457 | float max_load_factor() const { return m_ht.max_load_factor(); } |
458 | void max_load_factor(float ml) { m_ht.max_load_factor(ml); } |
459 | |
460 | void rehash(size_type count) { m_ht.rehash(count); } |
461 | void reserve(size_type count) { m_ht.reserve(count); } |
462 | |
463 | |
464 | /* |
465 | * Observers |
466 | */ |
467 | hasher hash_function() const { return m_ht.hash_function(); } |
468 | key_equal key_eq() const { return m_ht.key_eq(); } |
469 | |
470 | |
471 | /* |
472 | * Other |
473 | */ |
474 | |
475 | /** |
476 | * Convert a const_iterator to an iterator. |
477 | */ |
478 | iterator mutable_iterator(const_iterator pos) { |
479 | return m_ht.mutable_iterator(pos); |
480 | } |
481 | |
482 | size_type overflow_size() const noexcept { return m_ht.overflow_size(); } |
483 | |
484 | friend bool operator==(const hopscotch_set& lhs, const hopscotch_set& rhs) { |
485 | if(lhs.size() != rhs.size()) { |
486 | return false; |
487 | } |
488 | |
489 | for(const auto& element_lhs : lhs) { |
490 | const auto it_element_rhs = rhs.find(element_lhs); |
491 | if(it_element_rhs == rhs.cend()) { |
492 | return false; |
493 | } |
494 | } |
495 | |
496 | return true; |
497 | } |
498 | |
499 | friend bool operator!=(const hopscotch_set& lhs, const hopscotch_set& rhs) { |
500 | return !operator==(lhs, rhs); |
501 | } |
502 | |
503 | friend void swap(hopscotch_set& lhs, hopscotch_set& rhs) { |
504 | lhs.swap(rhs); |
505 | } |
506 | |
507 | private: |
508 | ht m_ht; |
509 | }; |
510 | |
511 | } // end namespace tsl |
512 | |
513 | #endif |
514 | |