Serenity Operating System
at hosted 382 lines 11 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#pragma once 28 29#include <AK/Assertions.h> 30#include <AK/SinglyLinkedList.h> 31#include <AK/StdLibExtras.h> 32#include <AK/TemporaryChange.h> 33#include <AK/Traits.h> 34 35namespace AK { 36 37template<typename T, typename> 38class HashTable; 39 40template<typename HashTableType, typename ElementType, typename BucketIteratorType> 41class HashTableIterator { 42public: 43 bool operator!=(const HashTableIterator& other) const 44 { 45 if (m_is_end && other.m_is_end) 46 return false; 47 return &m_table != &other.m_table 48 || m_is_end != other.m_is_end 49 || m_bucket_index != other.m_bucket_index 50 || m_bucket_iterator != other.m_bucket_iterator; 51 } 52 bool operator==(const HashTableIterator& other) const { return !(*this != other); } 53 ElementType& operator*() { return *m_bucket_iterator; } 54 ElementType* operator->() { return m_bucket_iterator.operator->(); } 55 HashTableIterator& operator++() 56 { 57 skip_to_next(); 58 return *this; 59 } 60 61 void skip_to_next() 62 { 63 while (!m_is_end) { 64 if (m_bucket_iterator.is_end()) { 65 ++m_bucket_index; 66 if (m_bucket_index >= m_table.capacity()) { 67 m_is_end = true; 68 return; 69 } 70 m_bucket_iterator = m_table.bucket(m_bucket_index).begin(); 71 } else { 72 ++m_bucket_iterator; 73 } 74 if (!m_bucket_iterator.is_end()) 75 return; 76 } 77 } 78 79private: 80 friend HashTableType; 81 82 explicit HashTableIterator(HashTableType& table, bool is_end, BucketIteratorType bucket_iterator = {}, size_t bucket_index = 0) 83 : m_table(table) 84 , m_bucket_index(bucket_index) 85 , m_is_end(is_end) 86 , m_bucket_iterator(bucket_iterator) 87 { 88 ASSERT(!table.m_clearing); 89 ASSERT(!table.m_rehashing); 90 if (!is_end && !m_table.is_empty() && m_bucket_iterator.is_end()) { 91 m_bucket_iterator = m_table.bucket(0).begin(); 92 if (m_bucket_iterator.is_end()) 93 skip_to_next(); 94 } 95 } 96 97 HashTableType& m_table; 98 size_t m_bucket_index { 0 }; 99 bool m_is_end { false }; 100 BucketIteratorType m_bucket_iterator; 101}; 102 103template<typename T, typename TraitsForT> 104class HashTable { 105private: 106 using Bucket = SinglyLinkedList<T>; 107 108public: 109 HashTable() {} 110 HashTable(const HashTable& other) 111 { 112 ensure_capacity(other.size()); 113 for (auto& it : other) 114 set(it); 115 } 116 HashTable& operator=(const HashTable& other) 117 { 118 if (this != &other) { 119 clear(); 120 ensure_capacity(other.size()); 121 for (auto& it : other) 122 set(it); 123 } 124 return *this; 125 } 126 HashTable(HashTable&& other) 127 : m_buckets(other.m_buckets) 128 , m_size(other.m_size) 129 , m_capacity(other.m_capacity) 130 { 131 other.m_size = 0; 132 other.m_capacity = 0; 133 other.m_buckets = nullptr; 134 } 135 HashTable& operator=(HashTable&& other) 136 { 137 if (this != &other) { 138 clear(); 139 m_buckets = other.m_buckets; 140 m_size = other.m_size; 141 m_capacity = other.m_capacity; 142 other.m_size = 0; 143 other.m_capacity = 0; 144 other.m_buckets = nullptr; 145 } 146 return *this; 147 } 148 149 ~HashTable() { clear(); } 150 bool is_empty() const { return !m_size; } 151 size_t size() const { return m_size; } 152 size_t capacity() const { return m_capacity; } 153 154 void ensure_capacity(size_t capacity) 155 { 156 ASSERT(capacity >= size()); 157 rehash(capacity); 158 } 159 160 void set(const T&); 161 void set(T&&); 162 bool contains(const T&) const; 163 void clear(); 164 165 using Iterator = HashTableIterator<HashTable, T, typename Bucket::Iterator>; 166 friend Iterator; 167 Iterator begin() { return Iterator(*this, is_empty()); } 168 Iterator end() { return Iterator(*this, true); } 169 170 using ConstIterator = HashTableIterator<const HashTable, const T, typename Bucket::ConstIterator>; 171 friend ConstIterator; 172 ConstIterator begin() const { return ConstIterator(*this, is_empty()); } 173 ConstIterator end() const { return ConstIterator(*this, true); } 174 175 template<typename Finder> 176 Iterator find(unsigned hash, Finder finder) 177 { 178 if (is_empty()) 179 return end(); 180 size_t bucket_index; 181 auto& bucket = lookup_with_hash(hash, &bucket_index); 182 auto bucket_iterator = bucket.find(finder); 183 if (bucket_iterator != bucket.end()) 184 return Iterator(*this, false, bucket_iterator, bucket_index); 185 return end(); 186 } 187 188 template<typename Finder> 189 ConstIterator find(unsigned hash, Finder finder) const 190 { 191 if (is_empty()) 192 return end(); 193 size_t bucket_index; 194 auto& bucket = lookup_with_hash(hash, &bucket_index); 195 auto bucket_iterator = bucket.find(finder); 196 if (bucket_iterator != bucket.end()) 197 return ConstIterator(*this, false, bucket_iterator, bucket_index); 198 return end(); 199 } 200 201 Iterator find(const T& value) 202 { 203 return find(TraitsForT::hash(value), [&](auto& other) { return TraitsForT::equals(value, other); }); 204 } 205 206 ConstIterator find(const T& value) const 207 { 208 return find(TraitsForT::hash(value), [&](auto& other) { return TraitsForT::equals(value, other); }); 209 } 210 211 void remove(const T& value) 212 { 213 auto it = find(value); 214 if (it != end()) 215 remove(it); 216 } 217 218 void remove(Iterator); 219 220private: 221 Bucket& lookup(const T&, size_t* bucket_index = nullptr); 222 const Bucket& lookup(const T&, size_t* bucket_index = nullptr) const; 223 224 Bucket& lookup_with_hash(unsigned hash, size_t* bucket_index) 225 { 226 if (bucket_index) 227 *bucket_index = hash % m_capacity; 228 return m_buckets[hash % m_capacity]; 229 } 230 231 const Bucket& lookup_with_hash(unsigned hash, size_t* bucket_index) const 232 { 233 if (bucket_index) 234 *bucket_index = hash % m_capacity; 235 return m_buckets[hash % m_capacity]; 236 } 237 238 void rehash(size_t capacity); 239 void insert(const T&); 240 void insert(T&&); 241 242 Bucket& bucket(size_t index) { return m_buckets[index]; } 243 const Bucket& bucket(size_t index) const { return m_buckets[index]; } 244 245 Bucket* m_buckets { nullptr }; 246 247 size_t m_size { 0 }; 248 size_t m_capacity { 0 }; 249 bool m_clearing { false }; 250 bool m_rehashing { false }; 251}; 252 253template<typename T, typename TraitsForT> 254void HashTable<T, TraitsForT>::set(T&& value) 255{ 256 if (!m_capacity) 257 rehash(1); 258 auto& bucket = lookup(value); 259 for (auto& e : bucket) { 260 if (TraitsForT::equals(e, value)) { 261 e = move(value); 262 return; 263 } 264 } 265 if (size() >= capacity()) { 266 rehash(size() + 1); 267 insert(move(value)); 268 } else { 269 bucket.append(move(value)); 270 } 271 m_size++; 272} 273 274template<typename T, typename TraitsForT> 275void HashTable<T, TraitsForT>::set(const T& value) 276{ 277 if (!m_capacity) 278 rehash(1); 279 auto& bucket = lookup(value); 280 for (auto& e : bucket) { 281 if (TraitsForT::equals(e, value)) { 282 e = value; 283 return; 284 } 285 } 286 if (size() >= capacity()) { 287 rehash(size() + 1); 288 insert(value); 289 } else { 290 bucket.append(value); 291 } 292 m_size++; 293} 294 295template<typename T, typename TraitsForT> 296void HashTable<T, TraitsForT>::rehash(size_t new_capacity) 297{ 298 TemporaryChange<bool> change(m_rehashing, true); 299 new_capacity *= 2; 300 auto* new_buckets = new Bucket[new_capacity]; 301 auto* old_buckets = m_buckets; 302 size_t old_capacity = m_capacity; 303 m_buckets = new_buckets; 304 m_capacity = new_capacity; 305 306 for (size_t i = 0; i < old_capacity; ++i) { 307 for (auto& value : old_buckets[i]) { 308 insert(move(value)); 309 } 310 } 311 312 delete[] old_buckets; 313} 314 315template<typename T, typename TraitsForT> 316void HashTable<T, TraitsForT>::clear() 317{ 318 TemporaryChange<bool> change(m_clearing, true); 319 if (m_buckets) { 320 delete[] m_buckets; 321 m_buckets = nullptr; 322 } 323 m_capacity = 0; 324 m_size = 0; 325} 326 327template<typename T, typename TraitsForT> 328void HashTable<T, TraitsForT>::insert(T&& value) 329{ 330 auto& bucket = lookup(value); 331 bucket.append(move(value)); 332} 333 334template<typename T, typename TraitsForT> 335void HashTable<T, TraitsForT>::insert(const T& value) 336{ 337 auto& bucket = lookup(value); 338 bucket.append(value); 339} 340 341template<typename T, typename TraitsForT> 342bool HashTable<T, TraitsForT>::contains(const T& value) const 343{ 344 if (is_empty()) 345 return false; 346 auto& bucket = lookup(value); 347 for (auto& e : bucket) { 348 if (TraitsForT::equals(e, value)) 349 return true; 350 } 351 return false; 352} 353 354template<typename T, typename TraitsForT> 355void HashTable<T, TraitsForT>::remove(Iterator it) 356{ 357 ASSERT(!is_empty()); 358 m_buckets[it.m_bucket_index].remove(it.m_bucket_iterator); 359 --m_size; 360} 361 362template<typename T, typename TraitsForT> 363auto HashTable<T, TraitsForT>::lookup(const T& value, size_t* bucket_index) -> Bucket& 364{ 365 unsigned hash = TraitsForT::hash(value); 366 if (bucket_index) 367 *bucket_index = hash % m_capacity; 368 return m_buckets[hash % m_capacity]; 369} 370 371template<typename T, typename TraitsForT> 372auto HashTable<T, TraitsForT>::lookup(const T& value, size_t* bucket_index) const -> const Bucket& 373{ 374 unsigned hash = TraitsForT::hash(value); 375 if (bucket_index) 376 *bucket_index = hash % m_capacity; 377 return m_buckets[hash % m_capacity]; 378} 379 380} 381 382using AK::HashTable;