├── LICENSE-CC0 ├── LICENSE-MIT ├── Makefile ├── README.md ├── include ├── flat_hash_map.hpp ├── flat_hash_set.hpp └── flat_hash_table.hpp └── test.cpp /LICENSE-CC0: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run_test: test 2 | ./test 3 | 4 | test: include/*.hpp test.cpp 5 | c++ -Wall -std=c++14 -Iinclude test.cpp -o test 6 | 7 | clean: 8 | rm -f test 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an implementation of a c++ hash set / map using open addressing, linear 2 | probing, and robin hood bucket stealing. It is a zero-dependency copy of the 3 | implementation I wrote for Starbound. 4 | 5 | It has three main design goals, roughly in inverse order of importance: 6 | 7 | - Be faster than std::unordered_map and std::unordered_set, focusing on forward 8 | iteration and find, including find with missing keys. Everything else just 9 | needs to be not worse than std::unordered_map. 10 | - Be as compatible with std::unordered_map and std::unordered_set as possible 11 | - Don't be clever 12 | 13 | It contains a MOSTLY compatible re-implementation of std::unordered_map and 14 | std::unordered_set, with the main exceptions being pointer stability, and the 15 | bucket API. With the standard implementations, one may assume that the pointers 16 | to keys and values in std::unordered_map and std::unordered_set will stay the 17 | same as long as the value exists in the map / set. That is very much NOT true 18 | with these, due to the nature of array backed open address hash tables, they 19 | will be changed on a rehash. 20 | 21 | There has been very little micro-optimization done, these are mostly written to 22 | just be simple. Still, they are, at least for Starbound, much faster than 23 | std::unordered_map and std::unordered_set (because it's not hard!). 24 | 25 | ## BAD PARTS ## 26 | 27 | - **Probably don't use this for anything important or depend on this repo!** It 28 | may however be useful for educational purposes for how to implement hash sets 29 | / maps in C++, or as a starting point for your own. 30 | 31 | - The included "tests" are are basically non-existent, and there are NO 32 | performance tests whatsoever. The performance testing I DID do was mostly 33 | inside Starbound, where there was a measurable improvement. 34 | 35 | - I spent very little effort thinking about extreme corner case behavior like 36 | throwing move constructors etc, so there are definitely limitations there. 37 | 38 | - The bucket vector size is always a power of two, there is nothing clever 39 | going on like using prime number bucket counts or anything like that to get 40 | around a bad std::hash implementation. It is possible that, especially with 41 | the default hash functions on integers in c++ that this may cause problems 42 | for certiain key sets. 43 | 44 | - Even though the point was compatibility, there are some methods that would be 45 | very easy to implement that are missing simply because Starbound didn't use 46 | them. 47 | 48 | ## WHY DID YOU EVEN DO THIS ## 49 | 50 | I'm sure there are better hash tables, in fact I tried a lot of them first. I 51 | had a specific hard requirement that the type be compatible with 52 | std::unordered_map, including accepting the same template parameters, all the 53 | same typedefs (const keys!), etc. 54 | 55 | Starbound has a specific style decision to ignore the possibility that move 56 | constructors or move assignment operators can possibly throw. On inspection of 57 | some alternatives that I tried seemed to be doing a lot of 58 | std::is_nothrow_move_constructible dancing, and Starbound hit all the slow 59 | paths. Making types in Starbound be std::is_nothrow_move_constructible turned 60 | out to be more painful than writing this hashtable. 61 | 62 | I also noticed some very bad pathalogical behavior on several of the hashmaps I 63 | tried, and I spent so long reading the code and working out why the pathalogical 64 | behavior was happening that the easiest way to understand it became to make my 65 | own hashmap. 66 | 67 | I have very bad NIH syndrome sometimes, and an occasional inability to 68 | understand something until I do it myself. 69 | 70 | ## License ## 71 | 72 | This code is licensed under either of: 73 | 74 | * MIT license [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT 75 | * Creative Commons CC0 1.0 Universal Public Domain Dedication 76 | [LICENSE-CC0](LICENSE-CC0) or 77 | https://creativecommons.org/publicdomain/zero/1.0/ 78 | 79 | at your option. 80 | -------------------------------------------------------------------------------- /include/flat_hash_map.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "flat_hash_table.hpp" 7 | 8 | namespace flat_hash { 9 | 10 | template , typename Equals = std::equal_to, typename Allocator = std::allocator> 11 | class hash_map { 12 | public: 13 | typedef Key key_type; 14 | typedef Mapped mapped_type; 15 | typedef std::pair value_type; 16 | typedef size_t size_type; 17 | typedef ptrdiff_t difference_type; 18 | typedef Hash hasher; 19 | typedef Equals key_equal; 20 | typedef Allocator allocator_type; 21 | typedef value_type& reference; 22 | typedef value_type const& const_reference; 23 | typedef value_type* pointer; 24 | typedef value_type const* const_pointer; 25 | 26 | private: 27 | typedef std::pair TableValue; 28 | 29 | struct GetKey { 30 | key_type const& operator()(TableValue const& value) const; 31 | }; 32 | 33 | typedef hash_table::other> Table; 34 | 35 | public: 36 | struct const_iterator { 37 | typedef std::forward_iterator_tag iterator_category; 38 | typedef typename hash_map::value_type const value_type; 39 | typedef ptrdiff_t difference_type; 40 | typedef value_type* pointer; 41 | typedef value_type& reference; 42 | 43 | bool operator==(const_iterator const& rhs) const; 44 | bool operator!=(const_iterator const& rhs) const; 45 | 46 | const_iterator& operator++(); 47 | const_iterator operator++(int); 48 | 49 | value_type& operator*() const; 50 | value_type* operator->() const; 51 | 52 | typename Table::const_iterator inner; 53 | }; 54 | 55 | struct iterator { 56 | typedef std::forward_iterator_tag iterator_category; 57 | typedef typename hash_map::value_type value_type; 58 | typedef ptrdiff_t difference_type; 59 | typedef value_type* pointer; 60 | typedef value_type& reference; 61 | 62 | bool operator==(iterator const& rhs) const; 63 | bool operator!=(iterator const& rhs) const; 64 | 65 | iterator& operator++(); 66 | iterator operator++(int); 67 | 68 | value_type& operator*() const; 69 | value_type* operator->() const; 70 | 71 | operator const_iterator() const; 72 | 73 | typename Table::iterator inner; 74 | }; 75 | 76 | hash_map(); 77 | explicit hash_map(size_t bucketCount, hasher const& hash = hasher(), 78 | key_equal const& equal = key_equal(), allocator_type const& alloc = allocator_type()); 79 | hash_map(size_t bucketCount, allocator_type const& alloc); 80 | hash_map(size_t bucketCount, hasher const& hash, allocator_type const& alloc); 81 | explicit hash_map(allocator_type const& alloc); 82 | 83 | template 84 | hash_map(InputIt first, InputIt last, size_t bucketCount = 0, 85 | hasher const& hash = hasher(), key_equal const& equal = key_equal(), 86 | allocator_type const& alloc = allocator_type()); 87 | template 88 | hash_map(InputIt first, InputIt last, size_t bucketCount, allocator_type const& alloc); 89 | template 90 | hash_map(InputIt first, InputIt last, size_t bucketCount, 91 | hasher const& hash, allocator_type const& alloc); 92 | 93 | hash_map(hash_map const& other); 94 | hash_map(hash_map const& other, allocator_type const& alloc); 95 | hash_map(hash_map&& other); 96 | hash_map(hash_map&& other, allocator_type const& alloc); 97 | 98 | hash_map(std::initializer_list init, size_t bucketCount = 0, 99 | hasher const& hash = hasher(), key_equal const& equal = key_equal(), 100 | allocator_type const& alloc = allocator_type()); 101 | hash_map(std::initializer_list init, size_t bucketCount, allocator_type const& alloc); 102 | hash_map(std::initializer_list init, size_t bucketCount, hasher const& hash, 103 | allocator_type const& alloc); 104 | 105 | hash_map& operator=(hash_map const& other); 106 | hash_map& operator=(hash_map&& other); 107 | hash_map& operator=(std::initializer_list init); 108 | 109 | iterator begin(); 110 | iterator end(); 111 | 112 | const_iterator begin() const; 113 | const_iterator end() const; 114 | 115 | const_iterator cbegin() const; 116 | const_iterator cend() const; 117 | 118 | size_t empty() const; 119 | size_t size() const; 120 | void clear(); 121 | 122 | std::pair insert(value_type const& value); 123 | template ::value>::type> 124 | std::pair insert(T&& value); 125 | iterator insert(const_iterator hint, value_type const& value); 126 | template ::value>::type> 127 | iterator insert(const_iterator hint, T&& value); 128 | template 129 | void insert(InputIt first, InputIt last); 130 | void insert(std::initializer_list init); 131 | 132 | template 133 | std::pair emplace(Args&&... args); 134 | template 135 | iterator emplace_hint(const_iterator hint, Args&&... args); 136 | 137 | iterator erase(const_iterator pos); 138 | iterator erase(const_iterator first, const_iterator last); 139 | size_t erase(key_type const& key); 140 | 141 | mapped_type& at(key_type const& key); 142 | mapped_type const& at(key_type const& key) const; 143 | 144 | mapped_type& operator[](key_type const& key); 145 | mapped_type& operator[](key_type&& key); 146 | 147 | size_t count(key_type const& key) const; 148 | const_iterator find(key_type const& key) const; 149 | iterator find(key_type const& key); 150 | std::pair equal_range(key_type const& key); 151 | std::pair equal_range(key_type const& key) const; 152 | 153 | void reserve(size_t capacity); 154 | 155 | bool operator==(hash_map const& rhs) const; 156 | bool operator!=(hash_map const& rhs) const; 157 | 158 | private: 159 | Table m_table; 160 | }; 161 | 162 | template 163 | auto hash_map::GetKey::operator()(TableValue const& value) const -> key_type const& { 164 | return value.first; 165 | } 166 | 167 | template 168 | bool hash_map::const_iterator::operator==(const_iterator const& rhs) const { 169 | return inner == rhs.inner; 170 | } 171 | 172 | template 173 | bool hash_map::const_iterator::operator!=(const_iterator const& rhs) const { 174 | return inner != rhs.inner; 175 | } 176 | 177 | template 178 | auto hash_map::const_iterator::operator++() -> const_iterator& { 179 | ++inner; 180 | return *this; 181 | } 182 | 183 | template 184 | auto hash_map::const_iterator::operator++(int) -> const_iterator { 185 | const_iterator copy(*this); 186 | ++*this; 187 | return copy; 188 | } 189 | 190 | template 191 | auto hash_map::const_iterator::operator*() const -> value_type& { 192 | return *operator->(); 193 | } 194 | 195 | template 196 | auto hash_map::const_iterator::operator->() const -> value_type* { 197 | return (value_type*)(&*inner); 198 | } 199 | 200 | template 201 | bool hash_map::iterator::operator==(iterator const& rhs) const { 202 | return inner == rhs.inner; 203 | } 204 | 205 | template 206 | bool hash_map::iterator::operator!=(iterator const& rhs) const { 207 | return inner != rhs.inner; 208 | } 209 | 210 | template 211 | auto hash_map::iterator::operator++() -> iterator& { 212 | ++inner; 213 | return *this; 214 | } 215 | 216 | template 217 | auto hash_map::iterator::operator++(int) -> iterator { 218 | iterator copy(*this); 219 | operator++(); 220 | return copy; 221 | } 222 | 223 | template 224 | auto hash_map::iterator::operator*() const -> value_type& { 225 | return *operator->(); 226 | } 227 | 228 | template 229 | auto hash_map::iterator::operator->() const -> value_type* { 230 | return (value_type*)(&*inner); 231 | } 232 | 233 | template 234 | hash_map::iterator::operator typename hash_map::const_iterator() const { 235 | return const_iterator{inner}; 236 | } 237 | 238 | template 239 | hash_map::hash_map() 240 | : hash_map(0) {} 241 | 242 | template 243 | hash_map::hash_map(size_t bucketCount, hasher const& hash, 244 | key_equal const& equal, allocator_type const& alloc) 245 | : m_table(bucketCount, GetKey(), hash, equal, alloc) {} 246 | 247 | template 248 | hash_map::hash_map(size_t bucketCount, allocator_type const& alloc) 249 | : hash_map(bucketCount, hasher(), key_equal(), alloc) {} 250 | 251 | template 252 | hash_map::hash_map(size_t bucketCount, hasher const& hash, 253 | allocator_type const& alloc) 254 | : hash_map(bucketCount, hash, key_equal(), alloc) {} 255 | 256 | template 257 | hash_map::hash_map(allocator_type const& alloc) 258 | : hash_map(0, hasher(), key_equal(), alloc) {} 259 | 260 | template 261 | template 262 | hash_map::hash_map(InputIt first, InputIt last, size_t bucketCount, 263 | hasher const& hash, key_equal const& equal, allocator_type const& alloc) 264 | : hash_map(bucketCount, hash, equal, alloc) { 265 | insert(first, last); 266 | } 267 | 268 | template 269 | template 270 | hash_map::hash_map(InputIt first, InputIt last, size_t bucketCount, 271 | allocator_type const& alloc) 272 | : hash_map(first, last, bucketCount, hasher(), key_equal(), alloc) {} 273 | 274 | template 275 | template 276 | hash_map::hash_map(InputIt first, InputIt last, size_t bucketCount, 277 | hasher const& hash, allocator_type const& alloc) 278 | : hash_map(first, last, bucketCount, hash, key_equal(), alloc) {} 279 | 280 | template 281 | hash_map::hash_map(hash_map const& other) 282 | : hash_map(other, other.m_table.getAllocator()) {} 283 | 284 | template 285 | hash_map::hash_map(hash_map const& other, allocator_type const& alloc) 286 | : hash_map(alloc) { 287 | operator=(other); 288 | } 289 | 290 | template 291 | hash_map::hash_map(hash_map&& other) 292 | : hash_map(move(other), other.m_table.getAllocator()) {} 293 | 294 | template 295 | hash_map::hash_map(hash_map&& other, allocator_type const& alloc) 296 | : hash_map(alloc) { 297 | operator=(move(other)); 298 | } 299 | 300 | template 301 | hash_map::hash_map(std::initializer_list init, size_t bucketCount, hasher const& hash, 302 | key_equal const& equal, allocator_type const& alloc) 303 | : hash_map(bucketCount, hash, equal, alloc) { 304 | operator=(init); 305 | } 306 | 307 | template 308 | hash_map::hash_map(std::initializer_list init, size_t bucketCount, 309 | allocator_type const& alloc) 310 | : hash_map(init, bucketCount, hasher(), key_equal(), alloc) {} 311 | 312 | template 313 | hash_map::hash_map(std::initializer_list init, size_t bucketCount, hasher const& hash, 314 | allocator_type const& alloc) 315 | : hash_map(init, bucketCount, hash, key_equal(), alloc) {} 316 | 317 | template 318 | auto hash_map::operator=(hash_map const& other) -> hash_map& { 319 | m_table.clear(); 320 | m_table.reserve(other.size()); 321 | for (auto const& p : other) 322 | m_table.insert(p); 323 | return *this; 324 | } 325 | 326 | template 327 | auto hash_map::operator=(hash_map&& other) -> hash_map& { 328 | m_table = move(other.m_table); 329 | return *this; 330 | } 331 | 332 | template 333 | auto hash_map::operator=(std::initializer_list init) -> hash_map& { 334 | clear(); 335 | insert(init.begin(), init.end()); 336 | return *this; 337 | } 338 | 339 | template 340 | auto hash_map::begin() -> iterator { 341 | return iterator{m_table.begin()}; 342 | } 343 | 344 | template 345 | auto hash_map::end() -> iterator { 346 | return iterator{m_table.end()}; 347 | } 348 | 349 | template 350 | auto hash_map::begin() const -> const_iterator { 351 | return const_iterator{m_table.begin()}; 352 | } 353 | 354 | template 355 | auto hash_map::end() const -> const_iterator { 356 | return const_iterator{m_table.end()}; 357 | } 358 | 359 | template 360 | auto hash_map::cbegin() const -> const_iterator { 361 | return begin(); 362 | } 363 | 364 | template 365 | auto hash_map::cend() const -> const_iterator { 366 | return end(); 367 | } 368 | 369 | template 370 | size_t hash_map::empty() const { 371 | return m_table.empty(); 372 | } 373 | 374 | template 375 | size_t hash_map::size() const { 376 | return m_table.size(); 377 | } 378 | 379 | template 380 | void hash_map::clear() { 381 | m_table.clear(); 382 | } 383 | 384 | template 385 | auto hash_map::insert(value_type const& value) -> std::pair { 386 | auto res = m_table.insert(TableValue(value)); 387 | return {iterator{res.first}, res.second}; 388 | } 389 | 390 | template 391 | template 392 | auto hash_map::insert(T&& value) -> std::pair { 393 | auto res = m_table.insert(TableValue(std::forward(value))); 394 | return {iterator{res.first}, res.second}; 395 | } 396 | 397 | template 398 | auto hash_map::insert(const_iterator hint, value_type const& value) -> iterator { 399 | return insert(hint, TableValue(value)); 400 | } 401 | 402 | template 403 | template 404 | auto hash_map::insert(const_iterator, T&& value) -> iterator { 405 | return insert(std::forward(value)).first; 406 | } 407 | 408 | template 409 | template 410 | void hash_map::insert(InputIt first, InputIt last) { 411 | m_table.reserve(m_table.size() + std::distance(first, last)); 412 | for (auto i = first; i != last; ++i) 413 | m_table.insert(*i); 414 | } 415 | 416 | template 417 | void hash_map::insert(std::initializer_list init) { 418 | insert(init.begin(), init.end()); 419 | } 420 | 421 | template 422 | template 423 | auto hash_map::emplace(Args&&... args) -> std::pair { 424 | return insert(TableValue(std::forward(args)...)); 425 | } 426 | 427 | template 428 | template 429 | auto hash_map::emplace_hint(const_iterator hint, Args&&... args) -> iterator { 430 | return insert(hint, TableValue(std::forward(args)...)); 431 | } 432 | 433 | template 434 | auto hash_map::erase(const_iterator pos) -> iterator { 435 | return iterator{m_table.erase(pos.inner)}; 436 | } 437 | 438 | template 439 | auto hash_map::erase(const_iterator first, const_iterator last) -> iterator { 440 | return iterator{m_table.erase(first.inner, last.inner)}; 441 | } 442 | 443 | template 444 | size_t hash_map::erase(key_type const& key) { 445 | auto i = m_table.find(key); 446 | if (i != m_table.end()) { 447 | m_table.erase(i); 448 | return 1; 449 | } 450 | return 0; 451 | } 452 | 453 | template 454 | auto hash_map::at(key_type const& key) -> mapped_type& { 455 | auto i = m_table.find(key); 456 | if (i == m_table.end()) 457 | throw std::out_of_range("no such key in hash_map"); 458 | return i->second; 459 | } 460 | 461 | template 462 | auto hash_map::at(key_type const& key) const -> mapped_type const& { 463 | auto i = m_table.find(key); 464 | if (i == m_table.end()) 465 | throw std::out_of_range("no such key in hash_map"); 466 | return i->second; 467 | } 468 | 469 | template 470 | auto hash_map::operator[](key_type const& key) -> mapped_type& { 471 | auto i = m_table.find(key); 472 | if (i != m_table.end()) 473 | return i->second; 474 | return m_table.insert({key, mapped_type()}).first->second; 475 | } 476 | 477 | template 478 | auto hash_map::operator[](key_type&& key) -> mapped_type& { 479 | auto i = m_table.find(key); 480 | if (i != m_table.end()) 481 | return i->second; 482 | return m_table.insert({move(key), mapped_type()}).first->second; 483 | } 484 | 485 | template 486 | size_t hash_map::count(key_type const& key) const { 487 | if (m_table.find(key) != m_table.end()) 488 | return 1; 489 | else 490 | return 0; 491 | } 492 | 493 | template 494 | auto hash_map::find(key_type const& key) const -> const_iterator { 495 | return const_iterator{m_table.find(key)}; 496 | } 497 | 498 | template 499 | auto hash_map::find(key_type const& key) -> iterator { 500 | return iterator{m_table.find(key)}; 501 | } 502 | 503 | template 504 | auto hash_map::equal_range(key_type const& key) -> std::pair { 505 | auto i = find(key); 506 | if (i != end()) { 507 | auto j = i; 508 | ++j; 509 | return {i, j}; 510 | } else { 511 | return {i, i}; 512 | } 513 | } 514 | 515 | template 516 | auto hash_map::equal_range(key_type const& key) const -> std::pair { 517 | auto i = find(key); 518 | if (i != end()) { 519 | auto j = i; 520 | ++j; 521 | return {i, j}; 522 | } else { 523 | return {i, i}; 524 | } 525 | } 526 | 527 | template 528 | void hash_map::reserve(size_t capacity) { 529 | m_table.reserve(capacity); 530 | } 531 | 532 | template 533 | bool hash_map::operator==(hash_map const& rhs) const { 534 | return m_table == rhs.m_table; 535 | } 536 | 537 | template 538 | bool hash_map::operator!=(hash_map const& rhs) const { 539 | return m_table != rhs.m_table; 540 | } 541 | 542 | } 543 | -------------------------------------------------------------------------------- /include/flat_hash_set.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "flat_hash_table.hpp" 6 | 7 | namespace flat_hash { 8 | 9 | template , typename Equals = std::equal_to, typename Allocator = std::allocator> 10 | class hash_set { 11 | public: 12 | typedef Key key_type; 13 | typedef Key value_type; 14 | typedef size_t size_type; 15 | typedef ptrdiff_t difference_type; 16 | typedef Hash hasher; 17 | typedef Equals key_equal; 18 | typedef Allocator allocator_type; 19 | typedef value_type& reference; 20 | typedef value_type const& const_reference; 21 | typedef value_type* pointer; 22 | typedef value_type const* const_pointer; 23 | 24 | private: 25 | struct GetKey { 26 | key_type const& operator()(value_type const& value) const; 27 | }; 28 | 29 | typedef hash_table Table; 30 | 31 | public: 32 | struct const_iterator { 33 | typedef std::forward_iterator_tag iterator_category; 34 | typedef typename hash_set::value_type const value_type; 35 | typedef ptrdiff_t difference_type; 36 | typedef value_type* pointer; 37 | typedef value_type& reference; 38 | 39 | bool operator==(const_iterator const& rhs) const; 40 | bool operator!=(const_iterator const& rhs) const; 41 | 42 | const_iterator& operator++(); 43 | const_iterator operator++(int); 44 | 45 | value_type& operator*() const; 46 | value_type* operator->() const; 47 | 48 | typename Table::const_iterator inner; 49 | }; 50 | 51 | struct iterator { 52 | typedef std::forward_iterator_tag iterator_category; 53 | typedef typename hash_set::value_type value_type; 54 | typedef ptrdiff_t difference_type; 55 | typedef value_type* pointer; 56 | typedef value_type& reference; 57 | 58 | bool operator==(iterator const& rhs) const; 59 | bool operator!=(iterator const& rhs) const; 60 | 61 | iterator& operator++(); 62 | iterator operator++(int); 63 | 64 | value_type& operator*() const; 65 | value_type* operator->() const; 66 | 67 | operator const_iterator() const; 68 | 69 | typename Table::iterator inner; 70 | }; 71 | 72 | hash_set(); 73 | explicit hash_set(size_t bucketCount, hasher const& hash = hasher(), 74 | key_equal const& equal = key_equal(), allocator_type const& alloc = allocator_type()); 75 | hash_set(size_t bucketCount, allocator_type const& alloc); 76 | hash_set(size_t bucketCount, hasher const& hash, allocator_type const& alloc); 77 | explicit hash_set(allocator_type const& alloc); 78 | 79 | template 80 | hash_set(InputIt first, InputIt last, size_t bucketCount = 0, 81 | hasher const& hash = hasher(), key_equal const& equal = key_equal(), 82 | allocator_type const& alloc = allocator_type()); 83 | template 84 | hash_set(InputIt first, InputIt last, size_t bucketCount, allocator_type const& alloc); 85 | template 86 | hash_set(InputIt first, InputIt last, size_t bucketCount, 87 | hasher const& hash, allocator_type const& alloc); 88 | 89 | hash_set(hash_set const& other); 90 | hash_set(hash_set const& other, allocator_type const& alloc); 91 | hash_set(hash_set&& other); 92 | hash_set(hash_set&& other, allocator_type const& alloc); 93 | 94 | hash_set(std::initializer_list init, size_t bucketCount = 0, 95 | hasher const& hash = hasher(), key_equal const& equal = key_equal(), 96 | allocator_type const& alloc = allocator_type()); 97 | hash_set(std::initializer_list init, size_t bucketCount, allocator_type const& alloc); 98 | hash_set(std::initializer_list init, size_t bucketCount, hasher const& hash, 99 | allocator_type const& alloc); 100 | 101 | hash_set& operator=(hash_set const& other); 102 | hash_set& operator=(hash_set&& other); 103 | hash_set& operator=(std::initializer_list init); 104 | 105 | iterator begin(); 106 | iterator end(); 107 | 108 | const_iterator begin() const; 109 | const_iterator end() const; 110 | 111 | const_iterator cbegin() const; 112 | const_iterator cend() const; 113 | 114 | size_t empty() const; 115 | size_t size() const; 116 | void clear(); 117 | 118 | std::pair insert(value_type const& value); 119 | std::pair insert(value_type&& value); 120 | iterator insert(const_iterator hint, value_type const& value); 121 | iterator insert(const_iterator hint, value_type&& value); 122 | template 123 | void insert(InputIt first, InputIt last); 124 | void insert(std::initializer_list init); 125 | 126 | template 127 | std::pair emplace(Args&&... args); 128 | template 129 | iterator emplace_hint(const_iterator hint, Args&&... args); 130 | 131 | iterator erase(const_iterator pos); 132 | iterator erase(const_iterator first, const_iterator last); 133 | size_t erase(key_type const& key); 134 | 135 | size_t count(key_type const& key) const; 136 | const_iterator find(key_type const& key) const; 137 | iterator find(key_type const& key); 138 | std::pair equal_range(key_type const& key); 139 | std::pair equal_range(key_type const& key) const; 140 | 141 | void reserve(size_t capacity); 142 | 143 | bool operator==(hash_set const& rhs) const; 144 | bool operator!=(hash_set const& rhs) const; 145 | 146 | private: 147 | Table m_table; 148 | }; 149 | 150 | template 151 | auto hash_set::GetKey::operator()(value_type const& value) const -> key_type const& { 152 | return value; 153 | } 154 | 155 | template 156 | bool hash_set::const_iterator::operator==(const_iterator const& rhs) const { 157 | return inner == rhs.inner; 158 | } 159 | 160 | template 161 | bool hash_set::const_iterator::operator!=(const_iterator const& rhs) const { 162 | return inner != rhs.inner; 163 | } 164 | 165 | template 166 | auto hash_set::const_iterator::operator++() -> const_iterator& { 167 | ++inner; 168 | return *this; 169 | } 170 | 171 | template 172 | auto hash_set::const_iterator::operator++(int) -> const_iterator { 173 | const_iterator copy(*this); 174 | operator++(); 175 | return copy; 176 | } 177 | 178 | template 179 | auto hash_set::const_iterator::operator*() const -> value_type& { 180 | return *inner; 181 | } 182 | 183 | template 184 | auto hash_set::const_iterator::operator->() const -> value_type* { 185 | return &operator*(); 186 | } 187 | 188 | template 189 | bool hash_set::iterator::operator==(iterator const& rhs) const { 190 | return inner == rhs.inner; 191 | } 192 | 193 | template 194 | bool hash_set::iterator::operator!=(iterator const& rhs) const { 195 | return inner != rhs.inner; 196 | } 197 | 198 | template 199 | auto hash_set::iterator::operator++() -> iterator& { 200 | ++inner; 201 | return *this; 202 | } 203 | 204 | template 205 | auto hash_set::iterator::operator++(int) -> iterator { 206 | iterator copy(*this); 207 | operator++(); 208 | return copy; 209 | } 210 | 211 | template 212 | auto hash_set::iterator::operator*() const -> value_type& { 213 | return *inner; 214 | } 215 | 216 | template 217 | auto hash_set::iterator::operator->() const -> value_type* { 218 | return &operator*(); 219 | } 220 | 221 | template 222 | hash_set::iterator::operator typename hash_set::const_iterator() const { 223 | return const_iterator{inner}; 224 | } 225 | 226 | template 227 | hash_set::hash_set() 228 | : hash_set(0) {} 229 | 230 | template 231 | hash_set::hash_set(size_t bucketCount, hasher const& hash, 232 | key_equal const& equal, allocator_type const& alloc) 233 | : m_table(bucketCount, GetKey(), hash, equal, alloc) {} 234 | 235 | template 236 | hash_set::hash_set(size_t bucketCount, allocator_type const& alloc) 237 | : hash_set(bucketCount, hasher(), key_equal(), alloc) {} 238 | 239 | template 240 | hash_set::hash_set(size_t bucketCount, hasher const& hash, 241 | allocator_type const& alloc) 242 | : hash_set(bucketCount, hash, key_equal(), alloc) {} 243 | 244 | template 245 | hash_set::hash_set(allocator_type const& alloc) 246 | : hash_set(0, hasher(), key_equal(), alloc) {} 247 | 248 | template 249 | template 250 | hash_set::hash_set(InputIt first, InputIt last, size_t bucketCount, 251 | hasher const& hash, key_equal const& equal, allocator_type const& alloc) 252 | : hash_set(bucketCount, hash, equal, alloc) { 253 | insert(first, last); 254 | } 255 | 256 | template 257 | template 258 | hash_set::hash_set(InputIt first, InputIt last, size_t bucketCount, 259 | allocator_type const& alloc) 260 | : hash_set(first, last, bucketCount, hasher(), key_equal(), alloc) {} 261 | 262 | template 263 | template 264 | hash_set::hash_set(InputIt first, InputIt last, size_t bucketCount, 265 | hasher const& hash, allocator_type const& alloc) 266 | : hash_set(first, last, bucketCount, hash, key_equal(), alloc) {} 267 | 268 | template 269 | hash_set::hash_set(hash_set const& other) 270 | : hash_set(other, other.m_table.getAllocator()) {} 271 | 272 | template 273 | hash_set::hash_set(hash_set const& other, allocator_type const& alloc) 274 | : hash_set(alloc) { 275 | operator=(other); 276 | } 277 | 278 | template 279 | hash_set::hash_set(hash_set&& other) 280 | : hash_set(std::move(other), other.m_table.getAllocator()) {} 281 | 282 | template 283 | hash_set::hash_set(hash_set&& other, allocator_type const& alloc) 284 | : hash_set(alloc) { 285 | operator=(std::move(other)); 286 | } 287 | 288 | template 289 | hash_set::hash_set(std::initializer_list init, size_t bucketCount, 290 | hasher const& hash, key_equal const& equal, allocator_type const& alloc) 291 | : hash_set(bucketCount, hash, equal, alloc) { 292 | operator=(init); 293 | } 294 | 295 | template 296 | hash_set::hash_set(std::initializer_list init, size_t bucketCount, allocator_type const& alloc) 297 | : hash_set(init, bucketCount, hasher(), key_equal(), alloc) {} 298 | 299 | template 300 | hash_set::hash_set(std::initializer_list init, size_t bucketCount, 301 | hasher const& hash, allocator_type const& alloc) 302 | : hash_set(init, bucketCount, hash, key_equal(), alloc) {} 303 | 304 | template 305 | hash_set& hash_set::operator=(hash_set const& other) { 306 | m_table.clear(); 307 | m_table.reserve(other.size()); 308 | for (auto const& p : other) 309 | m_table.insert(p); 310 | return *this; 311 | } 312 | 313 | template 314 | hash_set& hash_set::operator=(hash_set&& other) { 315 | m_table = std::move(other.m_table); 316 | return *this; 317 | } 318 | 319 | template 320 | hash_set& hash_set::operator=(std::initializer_list init) { 321 | clear(); 322 | insert(init.begin(), init.end()); 323 | return *this; 324 | } 325 | 326 | template 327 | auto hash_set::begin() -> iterator { 328 | return iterator{m_table.begin()}; 329 | } 330 | 331 | template 332 | auto hash_set::end() -> iterator { 333 | return iterator{m_table.end()}; 334 | } 335 | 336 | template 337 | auto hash_set::begin() const -> const_iterator { 338 | return const_iterator{m_table.begin()}; 339 | } 340 | 341 | template 342 | auto hash_set::end() const -> const_iterator { 343 | return const_iterator{m_table.end()}; 344 | } 345 | 346 | template 347 | auto hash_set::cbegin() const -> const_iterator { 348 | return begin(); 349 | } 350 | 351 | template 352 | auto hash_set::cend() const -> const_iterator { 353 | return end(); 354 | } 355 | 356 | template 357 | size_t hash_set::empty() const { 358 | return m_table.empty(); 359 | } 360 | 361 | template 362 | size_t hash_set::size() const { 363 | return m_table.size(); 364 | } 365 | 366 | template 367 | void hash_set::clear() { 368 | m_table.clear(); 369 | } 370 | 371 | template 372 | auto hash_set::insert(value_type const& value) -> std::pair { 373 | auto res = m_table.insert(value); 374 | return {iterator{res.first}, res.second}; 375 | } 376 | 377 | template 378 | auto hash_set::insert(value_type&& value) -> std::pair { 379 | auto res = m_table.insert(std::move(value)); 380 | return {iterator{res.first}, res.second}; 381 | } 382 | 383 | template 384 | auto hash_set::insert(const_iterator i, value_type const& value) -> iterator { 385 | return insert(i, value_type(value)); 386 | } 387 | 388 | template 389 | auto hash_set::insert(const_iterator, value_type&& value) -> iterator { 390 | return insert(std::move(value)).first; 391 | } 392 | 393 | template 394 | template 395 | void hash_set::insert(InputIt first, InputIt last) { 396 | m_table.reserve(m_table.size() + std::distance(first, last)); 397 | for (auto i = first; i != last; ++i) 398 | m_table.insert(*i); 399 | } 400 | 401 | template 402 | void hash_set::insert(std::initializer_list init) { 403 | insert(init.begin(), init.end()); 404 | } 405 | 406 | template 407 | template 408 | auto hash_set::emplace(Args&&... args) -> std::pair { 409 | return insert(value_type(std::forward(args)...)); 410 | } 411 | 412 | template 413 | template 414 | auto hash_set::emplace_hint(const_iterator i, Args&&... args) -> iterator { 415 | return insert(i, value_type(std::forward(args)...)); 416 | } 417 | 418 | template 419 | auto hash_set::erase(const_iterator pos) -> iterator { 420 | return iterator{m_table.erase(pos.inner)}; 421 | } 422 | 423 | template 424 | auto hash_set::erase(const_iterator first, const_iterator last) -> iterator { 425 | return iterator{m_table.erase(first.inner, last.inner)}; 426 | } 427 | 428 | template 429 | size_t hash_set::erase(key_type const& key) { 430 | auto i = m_table.find(key); 431 | if (i != m_table.end()) { 432 | m_table.erase(i); 433 | return 1; 434 | } 435 | return 0; 436 | } 437 | 438 | template 439 | size_t hash_set::count(Key const& key) const { 440 | if (m_table.find(key) != m_table.end()) 441 | return 1; 442 | else 443 | return 0; 444 | } 445 | 446 | template 447 | auto hash_set::find(key_type const& key) const -> const_iterator { 448 | return const_iterator{m_table.find(key)}; 449 | } 450 | 451 | template 452 | auto hash_set::find(key_type const& key) -> iterator { 453 | return iterator{m_table.find(key)}; 454 | } 455 | 456 | template 457 | auto hash_set::equal_range(key_type const& key) -> std::pair { 458 | auto i = find(key); 459 | if (i != end()) { 460 | auto j = i; 461 | ++j; 462 | return {i, j}; 463 | } else { 464 | return {i, i}; 465 | } 466 | } 467 | 468 | template 469 | auto hash_set::equal_range(key_type const& key) const -> std::pair { 470 | auto i = find(key); 471 | if (i != end()) { 472 | auto j = i; 473 | ++j; 474 | return {i, j}; 475 | } else { 476 | return {i, i}; 477 | } 478 | } 479 | 480 | template 481 | void hash_set::reserve(size_t capacity) { 482 | m_table.reserve(capacity); 483 | } 484 | 485 | template 486 | bool hash_set::operator==(hash_set const& rhs) const { 487 | return m_table == rhs.m_table; 488 | } 489 | 490 | template 491 | bool hash_set::operator!=(hash_set const& rhs) const { 492 | return m_table != rhs.m_table; 493 | } 494 | 495 | } 496 | -------------------------------------------------------------------------------- /include/flat_hash_table.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace flat_hash { 7 | 8 | template 9 | struct hash_table { 10 | private: 11 | static size_t const NPos = (size_t)-1; 12 | static size_t const EmptyHashValue = 0; 13 | static size_t const EndHashValue = 1; 14 | static size_t const FilledHashBit = (size_t)1 << (sizeof(size_t) * 8 - 1); 15 | 16 | struct Bucket { 17 | Bucket(); 18 | ~Bucket(); 19 | 20 | Bucket(Bucket const& rhs); 21 | Bucket(Bucket&& rhs); 22 | 23 | Bucket& operator=(Bucket const& rhs); 24 | Bucket& operator=(Bucket&& rhs); 25 | 26 | void setFilled(size_t hash, Value value); 27 | void setEmpty(); 28 | void setEnd(); 29 | 30 | Value const* valuePtr() const; 31 | Value* valuePtr(); 32 | bool isEmpty() const; 33 | bool isEnd() const; 34 | 35 | union { 36 | Value value; 37 | }; 38 | size_t hash; 39 | }; 40 | 41 | typedef std::vector::other> Buckets; 42 | 43 | public: 44 | struct const_iterator { 45 | bool operator==(const_iterator const& rhs) const; 46 | bool operator!=(const_iterator const& rhs) const; 47 | 48 | const_iterator& operator++(); 49 | const_iterator operator++(int); 50 | 51 | Value const& operator*() const; 52 | Value const* operator->() const; 53 | 54 | Bucket const* current; 55 | }; 56 | 57 | struct iterator { 58 | bool operator==(iterator const& rhs) const; 59 | bool operator!=(iterator const& rhs) const; 60 | 61 | iterator& operator++(); 62 | iterator operator++(int); 63 | 64 | Value& operator*() const; 65 | Value* operator->() const; 66 | 67 | operator const_iterator() const; 68 | 69 | Bucket* current; 70 | }; 71 | 72 | hash_table(size_t bucketCount, GetKey const& getKey, Hash const& hash, Equals const& equal, Allocator const& alloc); 73 | 74 | iterator begin(); 75 | iterator end(); 76 | 77 | const_iterator begin() const; 78 | const_iterator end() const; 79 | 80 | size_t empty() const; 81 | size_t size() const; 82 | void clear(); 83 | 84 | std::pair insert(Value value); 85 | 86 | iterator erase(const_iterator pos); 87 | iterator erase(const_iterator first, const_iterator last); 88 | 89 | const_iterator find(Key const& key) const; 90 | iterator find(Key const& key); 91 | 92 | void reserve(size_t capacity); 93 | Allocator getAllocator() const; 94 | 95 | bool operator==(hash_table const& rhs) const; 96 | bool operator!=(hash_table const& rhs) const; 97 | 98 | private: 99 | static constexpr size_t MinCapacity = 8; 100 | static constexpr double MaxFillLevel = 0.7; 101 | 102 | // Scans for the next bucket value that is non-empty 103 | static Bucket* scan(Bucket* p); 104 | static Bucket const* scan(Bucket const* p); 105 | 106 | size_t hashBucket(size_t hash) const; 107 | size_t bucketError(size_t current, size_t target) const; 108 | void checkCapacity(size_t additionalCapacity); 109 | 110 | Buckets m_buckets; 111 | size_t m_filledCount; 112 | 113 | GetKey m_getKey; 114 | Hash m_hash; 115 | Equals m_equals; 116 | }; 117 | 118 | template 119 | hash_table::Bucket::Bucket() { 120 | this->hash = EmptyHashValue; 121 | } 122 | 123 | template 124 | hash_table::Bucket::~Bucket() { 125 | if (auto s = valuePtr()) 126 | s->~Value(); 127 | } 128 | 129 | template 130 | hash_table::Bucket::Bucket(Bucket const& rhs) { 131 | this->hash = rhs.hash; 132 | if (auto o = rhs.valuePtr()) 133 | new (&this->value) Value(*o); 134 | } 135 | 136 | template 137 | hash_table::Bucket::Bucket(Bucket&& rhs) { 138 | this->hash = rhs.hash; 139 | if (auto o = rhs.valuePtr()) 140 | new (&this->value) Value(std::move(*o)); 141 | } 142 | 143 | template 144 | auto hash_table::Bucket::operator=(Bucket const& rhs) -> Bucket& { 145 | if (auto o = rhs.valuePtr()) { 146 | if (auto s = valuePtr()) 147 | *s = *o; 148 | else 149 | new (&this->value) Value(*o); 150 | } else { 151 | if (auto s = valuePtr()) 152 | s->~Value(); 153 | } 154 | this->hash = rhs.hash; 155 | return *this; 156 | } 157 | 158 | template 159 | auto hash_table::Bucket::operator=(Bucket&& rhs) -> Bucket& { 160 | if (auto o = rhs.valuePtr()) { 161 | if (auto s = valuePtr()) 162 | *s = std::move(*o); 163 | else 164 | new (&this->value) Value(std::move(*o)); 165 | } else { 166 | if (auto s = valuePtr()) 167 | s->~Value(); 168 | } 169 | this->hash = rhs.hash; 170 | return *this; 171 | } 172 | 173 | template 174 | void hash_table::Bucket::setFilled(size_t hash, Value value) { 175 | if (auto s = valuePtr()) 176 | *s = std::move(value); 177 | else 178 | new (&this->value) Value(std::move(value)); 179 | this->hash = hash | FilledHashBit; 180 | } 181 | 182 | template 183 | void hash_table::Bucket::setEmpty() { 184 | if (auto s = valuePtr()) 185 | s->~Value(); 186 | this->hash = EmptyHashValue; 187 | } 188 | 189 | template 190 | void hash_table::Bucket::setEnd() { 191 | if (auto s = valuePtr()) 192 | s->~Value(); 193 | this->hash = EndHashValue; 194 | } 195 | 196 | template 197 | Value const* hash_table::Bucket::valuePtr() const { 198 | if (hash & FilledHashBit) 199 | return &this->value; 200 | return nullptr; 201 | } 202 | 203 | template 204 | Value* hash_table::Bucket::valuePtr() { 205 | if (hash & FilledHashBit) 206 | return &this->value; 207 | return nullptr; 208 | } 209 | 210 | template 211 | bool hash_table::Bucket::isEmpty() const { 212 | return this->hash == EmptyHashValue; 213 | } 214 | 215 | template 216 | bool hash_table::Bucket::isEnd() const { 217 | return this->hash == EndHashValue; 218 | } 219 | 220 | template 221 | bool hash_table::const_iterator::operator==(const_iterator const& rhs) const { 222 | return current == rhs.current; 223 | } 224 | 225 | template 226 | bool hash_table::const_iterator::operator!=(const_iterator const& rhs) const { 227 | return current != rhs.current; 228 | } 229 | 230 | template 231 | auto hash_table::const_iterator::operator++() -> const_iterator& { 232 | current = scan(++current); 233 | return *this; 234 | } 235 | 236 | template 237 | auto hash_table::const_iterator::operator++(int) -> const_iterator { 238 | const_iterator copy(*this); 239 | operator++(); 240 | return copy; 241 | } 242 | 243 | template 244 | auto hash_table::const_iterator::operator*() const -> Value const& { 245 | return *operator->(); 246 | } 247 | 248 | template 249 | auto hash_table::const_iterator::operator->() const -> Value const* { 250 | return current->valuePtr(); 251 | } 252 | 253 | template 254 | bool hash_table::iterator::operator==(iterator const& rhs) const { 255 | return current == rhs.current; 256 | } 257 | 258 | template 259 | bool hash_table::iterator::operator!=(iterator const& rhs) const { 260 | return current != rhs.current; 261 | } 262 | 263 | template 264 | auto hash_table::iterator::operator++() -> iterator& { 265 | current = scan(++current); 266 | return *this; 267 | } 268 | 269 | template 270 | auto hash_table::iterator::operator++(int) -> iterator { 271 | iterator copy(*this); 272 | operator++(); 273 | return copy; 274 | } 275 | 276 | template 277 | auto hash_table::iterator::operator*() const -> Value& { 278 | return *operator->(); 279 | } 280 | 281 | template 282 | auto hash_table::iterator::operator->() const -> Value* { 283 | return current->valuePtr(); 284 | } 285 | 286 | template 287 | hash_table::iterator::operator typename hash_table::const_iterator() const { 288 | return const_iterator{current}; 289 | } 290 | 291 | template 292 | hash_table::hash_table(size_t bucketCount, 293 | GetKey const& getKey, Hash const& hash, Equals const& equal, Allocator const& alloc) 294 | : m_buckets(alloc), m_filledCount(0), m_getKey(getKey), 295 | m_hash(hash), m_equals(equal) { 296 | if (bucketCount != 0) 297 | checkCapacity(bucketCount); 298 | } 299 | 300 | template 301 | auto hash_table::begin() -> iterator { 302 | if (m_buckets.empty()) 303 | return end(); 304 | return iterator{scan(m_buckets.data())}; 305 | } 306 | 307 | template 308 | auto hash_table::end() -> iterator { 309 | return iterator{m_buckets.data() + m_buckets.size() - 1}; 310 | } 311 | 312 | template 313 | auto hash_table::begin() const -> const_iterator { 314 | return const_cast(this)->begin(); 315 | } 316 | 317 | template 318 | auto hash_table::end() const -> const_iterator { 319 | return const_cast(this)->end(); 320 | } 321 | 322 | template 323 | size_t hash_table::empty() const { 324 | return m_filledCount == 0; 325 | } 326 | 327 | template 328 | size_t hash_table::size() const { 329 | return m_filledCount; 330 | } 331 | 332 | template 333 | void hash_table::clear() { 334 | if (m_buckets.empty()) 335 | return; 336 | 337 | for (size_t i = 0; i < m_buckets.size() - 1; ++i) 338 | m_buckets[i].setEmpty(); 339 | m_filledCount = 0; 340 | } 341 | 342 | template 343 | auto hash_table::insert(Value value) -> std::pair { 344 | if (m_buckets.empty() || m_filledCount + 1 > (m_buckets.size() - 1) * MaxFillLevel) 345 | checkCapacity(1); 346 | 347 | size_t hash = m_hash(m_getKey(value)) | FilledHashBit; 348 | size_t targetBucket = hashBucket(hash); 349 | size_t currentBucket = targetBucket; 350 | size_t insertedBucket = NPos; 351 | 352 | while (true) { 353 | auto& target = m_buckets[currentBucket]; 354 | if (auto entryValue = target.valuePtr()) { 355 | if (target.hash == hash && m_equals(m_getKey(*entryValue), m_getKey(value))) 356 | return std::make_pair(iterator{m_buckets.data() + currentBucket}, false); 357 | 358 | size_t entryTargetBucket = hashBucket(target.hash); 359 | size_t entryError = bucketError(currentBucket, entryTargetBucket); 360 | size_t addError = bucketError(currentBucket, targetBucket); 361 | if (addError > entryError) { 362 | if (insertedBucket == NPos) 363 | insertedBucket = currentBucket; 364 | 365 | std::swap(value, *entryValue); 366 | std::swap(hash, target.hash); 367 | targetBucket = entryTargetBucket; 368 | } 369 | currentBucket = hashBucket(currentBucket + 1); 370 | 371 | } else { 372 | target.setFilled(hash, std::move(value)); 373 | ++m_filledCount; 374 | if (insertedBucket == NPos) 375 | insertedBucket = currentBucket; 376 | 377 | return std::make_pair(iterator{m_buckets.data() + insertedBucket}, true); 378 | } 379 | } 380 | } 381 | 382 | template 383 | auto hash_table::erase(const_iterator pos) -> iterator { 384 | size_t bucketIndex = pos.current - m_buckets.data(); 385 | size_t currentBucketIndex = bucketIndex; 386 | auto currentBucket = &m_buckets[currentBucketIndex]; 387 | 388 | while (true) { 389 | size_t nextBucketIndex = hashBucket(currentBucketIndex + 1); 390 | auto nextBucket = &m_buckets[nextBucketIndex]; 391 | if (auto nextPtr = nextBucket->valuePtr()) { 392 | if (bucketError(nextBucketIndex, nextBucket->hash) > 0) { 393 | currentBucket->hash = nextBucket->hash; 394 | *currentBucket->valuePtr() = std::move(*nextPtr); 395 | currentBucketIndex = nextBucketIndex; 396 | currentBucket = nextBucket; 397 | } else { 398 | break; 399 | } 400 | } else { 401 | break; 402 | } 403 | } 404 | 405 | m_buckets[currentBucketIndex].setEmpty(); 406 | --m_filledCount; 407 | 408 | return iterator{scan(m_buckets.data() + bucketIndex)}; 409 | } 410 | 411 | template 412 | auto hash_table::erase(const_iterator first, const_iterator last) -> iterator { 413 | while (first != last) 414 | first = erase(first); 415 | return iterator{(Bucket*)first.current}; 416 | } 417 | 418 | template 419 | auto hash_table::find(Key const& key) const -> const_iterator { 420 | return const_cast(this)->find(key); 421 | } 422 | 423 | template 424 | auto hash_table::find(Key const& key) -> iterator { 425 | if (m_buckets.empty()) 426 | return end(); 427 | 428 | size_t hash = m_hash(key) | FilledHashBit; 429 | size_t targetBucket = hashBucket(hash); 430 | size_t currentBucket = targetBucket; 431 | while (true) { 432 | auto& bucket = m_buckets[currentBucket]; 433 | if (auto value = bucket.valuePtr()) { 434 | if (bucket.hash == hash && m_equals(m_getKey(*value), key)) 435 | return iterator{m_buckets.data() + currentBucket}; 436 | 437 | size_t entryError = bucketError(currentBucket, bucket.hash); 438 | size_t findError = bucketError(currentBucket, targetBucket); 439 | 440 | if (findError > entryError) 441 | return end(); 442 | 443 | currentBucket = hashBucket(currentBucket + 1); 444 | 445 | } else { 446 | return end(); 447 | } 448 | } 449 | } 450 | 451 | template 452 | void hash_table::reserve(size_t capacity) { 453 | if (capacity > m_filledCount) 454 | checkCapacity(capacity - m_filledCount); 455 | } 456 | 457 | template 458 | Allocator hash_table::getAllocator() const { 459 | return m_buckets.get_allocator(); 460 | } 461 | 462 | template 463 | bool hash_table::operator==(hash_table const& rhs) const { 464 | if (size() != rhs.size()) 465 | return false; 466 | 467 | auto i = begin(); 468 | auto j = rhs.begin(); 469 | auto e = end(); 470 | 471 | while (i != e) { 472 | if (*i != *j) 473 | return false; 474 | ++i; 475 | ++j; 476 | } 477 | 478 | return true; 479 | } 480 | 481 | template 482 | bool hash_table::operator!=(hash_table const& rhs) const { 483 | return !operator==(rhs); 484 | } 485 | 486 | template 487 | constexpr size_t hash_table::MinCapacity; 488 | 489 | template 490 | constexpr double hash_table::MaxFillLevel; 491 | 492 | template 493 | auto hash_table::scan(Bucket* p) -> Bucket* { 494 | while (p->isEmpty()) 495 | ++p; 496 | return p; 497 | } 498 | 499 | template 500 | auto hash_table::scan(Bucket const* p) -> Bucket const* { 501 | while (p->isEmpty()) 502 | ++p; 503 | return p; 504 | } 505 | 506 | template 507 | size_t hash_table::hashBucket(size_t hash) const { 508 | return hash & (m_buckets.size() - 2); 509 | } 510 | 511 | template 512 | size_t hash_table::bucketError(size_t current, size_t target) const { 513 | return hashBucket(current - target); 514 | } 515 | 516 | template 517 | void hash_table::checkCapacity(size_t additionalCapacity) { 518 | size_t newSize; 519 | if (!m_buckets.empty()) 520 | newSize = m_buckets.size() - 1; 521 | else 522 | newSize = MinCapacity; 523 | 524 | while ((double)(m_filledCount + additionalCapacity) / (double)newSize > MaxFillLevel) 525 | newSize *= 2; 526 | 527 | if (newSize == m_buckets.size() - 1) 528 | return; 529 | 530 | Buckets oldBuckets; 531 | swap(m_buckets, oldBuckets); 532 | 533 | // Leave an extra end entry when allocating buckets, so that iterators are 534 | // simpler and can simply iterate until they find something that is not an 535 | // empty entry. 536 | m_buckets.resize(newSize + 1); 537 | while (m_buckets.capacity() > newSize * 2 + 1) { 538 | newSize *= 2; 539 | m_buckets.resize(newSize + 1); 540 | } 541 | m_buckets[newSize].setEnd(); 542 | 543 | m_filledCount = 0; 544 | 545 | for (auto& entry : oldBuckets) { 546 | if (auto ptr = entry.valuePtr()) 547 | insert(std::move(*ptr)); 548 | } 549 | } 550 | 551 | } 552 | -------------------------------------------------------------------------------- /test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "flat_hash_set.hpp" 5 | #include "flat_hash_map.hpp" 6 | 7 | using namespace flat_hash; 8 | 9 | void test_hash_set() { 10 | hash_set test_set = {42, 63}; 11 | assert(test_set.find(41) == test_set.end()); 12 | assert(*test_set.find(42) == 42); 13 | assert(*test_set.find(63) == 63); 14 | assert(test_set.find(64) == test_set.end()); 15 | assert(test_set.size() == 2u); 16 | 17 | test_set.erase(test_set.find(42)); 18 | assert(test_set.find(42) == test_set.end()); 19 | assert(*test_set.find(63) == 63); 20 | assert(test_set.size() == 1u); 21 | 22 | test_set.erase(test_set.find(63)); 23 | assert(test_set.find(42) == test_set.end()); 24 | assert(test_set.find(63) == test_set.end()); 25 | assert(test_set.size() == 0u); 26 | 27 | test_set.insert(12); 28 | test_set.insert(24); 29 | assert(*test_set.find(12) == 12); 30 | assert(test_set.size() == 2u); 31 | test_set.clear(); 32 | 33 | assert(test_set.find(12) == test_set.end()); 34 | assert(test_set.size() == 0u); 35 | 36 | assert(test_set.insert(7).second); 37 | assert(test_set.insert(11).second); 38 | assert(!test_set.insert(7).second); 39 | 40 | assert(test_set.size() == 2u); 41 | 42 | hash_set test_set2(test_set.begin(), test_set.end()); 43 | assert(test_set == test_set2); 44 | 45 | assert(test_set.erase(test_set.begin(), test_set.end()) == test_set.end()); 46 | assert(test_set.size() == 0u); 47 | 48 | assert(test_set != test_set2); 49 | 50 | hash_set test_set3(test_set.begin(), test_set.end()); 51 | assert(test_set3.size() == 0u); 52 | 53 | test_set2 = test_set; 54 | assert(test_set == test_set2); 55 | } 56 | 57 | void test_hash_map() { 58 | hash_map test_map = {{42, 42}, {63, 63}}; 59 | assert(test_map.find(41) == test_map.end()); 60 | assert(test_map.find(42)->second == 42); 61 | assert(test_map.find(63)->second == 63); 62 | assert(test_map.find(64) == test_map.end()); 63 | assert(test_map.size() == 2u); 64 | 65 | test_map.erase(test_map.find(42)); 66 | assert(test_map.find(42) == test_map.end()); 67 | assert(test_map.find(63)->second == 63); 68 | assert(test_map.size() == 1u); 69 | 70 | test_map.erase(test_map.find(63)); 71 | assert(test_map.find(42) == test_map.end()); 72 | assert(test_map.find(63) == test_map.end()); 73 | assert(test_map.size() == 0u); 74 | 75 | test_map.insert({12, 12}); 76 | test_map.insert({24, 24}); 77 | assert(test_map.find(12)->second == 12); 78 | assert(test_map.size() == 2u); 79 | test_map.clear(); 80 | 81 | assert(test_map.find(12) == test_map.end()); 82 | assert(test_map.size() == 0u); 83 | 84 | assert(test_map.insert({7, 7}).second); 85 | assert(test_map.insert({11, 11}).second); 86 | assert(!test_map.insert({7, 7}).second); 87 | 88 | assert(test_map.size() == 2u); 89 | 90 | hash_map test_map2(test_map.begin(), test_map.end()); 91 | assert(test_map == test_map2); 92 | 93 | assert(test_map.erase(test_map.begin(), test_map.end()) == test_map.end()); 94 | assert(test_map.size() == 0u); 95 | 96 | assert(test_map != test_map2); 97 | 98 | hash_map test_map3(test_map.begin(), test_map.end()); 99 | assert(test_map3.size() == 0u); 100 | 101 | test_map2 = test_map; 102 | assert(test_map == test_map2); 103 | } 104 | 105 | int main(int argc, char** argv) { 106 | test_hash_set(); 107 | test_hash_map(); 108 | std::cout << "tests passed!" << std::endl; 109 | return 0; 110 | } 111 | --------------------------------------------------------------------------------