src/HashTable.h
author David Anderson <dvander@alliedmods.net>
Sun Jan 06 13:56:56 2013 -0800 (2013-01-06)
changeset 257 9db4cc815ebc
parent 195 c1ba166f3fc4
permissions -rw-r--r--
Fix gcc and clang builds.
     1 /* vim: set ts=4 sw=4 tw=99 et:
     2  *
     3  * Copyright (C) 2012 David Anderson
     4  *
     5  * This file is part of SourcePawn.
     6  *
     7  * SourcePawn is free software: you can redistribute it and/or modify it under
     8  * the terms of the GNU General Public License as published by the Free
     9  * Software Foundation, either version 3 of the License, or (at your option)
    10  * any later version.
    11  * 
    12  * SourcePawn is distributed in the hope that it will be useful, but WITHOUT ANY
    13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
    15  *
    16  * You should have received a copy of the GNU General Public License along with
    17  * SourcePawn. If not, see http://www.gnu.org/licenses/.
    18  */
    19 #ifndef _INCLUDE_KEIMA_HASHTABLE_H_
    20 #define _INCLUDE_KEIMA_HASHTABLE_H_
    21 
    22 #include <new>
    23 #include <limits.h>
    24 #include "Utility.h"
    25 
    26 namespace ke {
    27 
    28 template <typename T>
    29 class HashTableEntry
    30 {
    31     uint32 hash_;
    32     T t_;
    33 
    34   public:
    35     static const uint32 kFreeHash = 0;
    36     static const uint32 kRemovedHash = 1;
    37 
    38   public:
    39     void setHash(uint32 hash) {
    40         hash_ = hash;
    41     }
    42     uint32 hash() const {
    43         return hash_;
    44     }
    45     void setRemoved() {
    46         hash_ = kRemovedHash;
    47         t_ = T();
    48     }
    49     void construct() {
    50         hash_ = kFreeHash;
    51         new (&t_) T();
    52     }
    53     void destruct() {
    54         t_.~T();
    55     }
    56     bool removed() const {
    57         return hash_ == kRemovedHash;
    58     }
    59     bool free() const {
    60         return hash_ == kFreeHash;
    61     }
    62     bool isLive() const {
    63         return hash_ > kRemovedHash;
    64     }
    65     T &payload() {
    66         return t_;
    67     }
    68     bool sameHash(uint32 hash) const {
    69         return hash_ == hash;
    70     }
    71 };
    72 
    73 template <typename HashPolicy, typename AllocPolicy>
    74 class HashTable : public AllocPolicy
    75 {
    76     friend class iterator;
    77 
    78     typedef typename HashPolicy::Payload Payload;
    79     typedef HashTableEntry<Payload> Entry;
    80 
    81     uint32 capacity_;
    82     uint32 nelements_;
    83     uint32 ndeleted_;
    84     Entry *table_;
    85     uint32 minCapacity_;
    86 
    87   private:
    88     static const uint32 kMinCapacity = 16;
    89     static const uint32 kMaxCapacity = INT_MAX / sizeof(Entry);
    90 
    91     template <typename Key>
    92     uint32 computeHash(const Key &key) {
    93         // Multiply by golden ratio.
    94         uint32 hash = HashPolicy::hash(key) * 0x9E3779B9;
    95         if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash)
    96             hash += 2;
    97         return hash;
    98     }
    99 
   100     Entry *createTable(uint32 capacity) {
   101         assert(capacity <= kMaxCapacity);
   102 
   103         Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry));
   104         if (!table)
   105             return NULL;
   106 
   107         for (size_t i = 0; i < capacity; i++)
   108             table[i].construct();
   109 
   110         return table;
   111     }
   112 
   113   public:
   114     class Result
   115     {
   116         friend class HashTable;
   117 
   118         Entry *entry_;
   119 
   120         Entry &entry() {
   121             return *entry_;
   122         }
   123 
   124       public:
   125         Result(Entry *entry)
   126           : entry_(entry)
   127         { }
   128 
   129         Payload * operator ->() {
   130             return &entry_->payload();
   131         }
   132         Payload & operator *() {
   133             return entry_->payload();
   134         }
   135 
   136         bool found() const {
   137             return entry_->isLive();
   138         }
   139     };
   140 
   141     class Insert : public Result
   142     {
   143         uint32 hash_;
   144 
   145       public:
   146         Insert(Entry *entry, uint32 hash)
   147           : Result(entry),
   148             hash_(hash)
   149         {
   150         }
   151 
   152         uint32 hash() const {
   153             return hash_;
   154         }
   155     };
   156 
   157   private:
   158     class Probulator {
   159         uint32 hash_;
   160         uint32 capacity_;
   161 
   162       public:
   163         Probulator(uint32 hash, uint32 capacity)
   164           : hash_(hash),
   165             capacity_(capacity)
   166         {
   167             assert(IsPowerOfTwo(capacity_)); 
   168         }
   169 
   170         uint32 entry() const {
   171             return hash_ & (capacity_ - 1);
   172         }
   173         uint32 next() {
   174             hash_++;
   175             return entry();
   176         }
   177     };
   178 
   179     bool underloaded() const {
   180         // Check if the table is underloaded: < 25% entries used.
   181         return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4);
   182     }
   183     bool overloaded() const {
   184         // Grow if the table is overloaded: > 75% entries used.
   185         return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4));
   186     }
   187 
   188     bool shrink() {
   189         if ((capacity_ >> 1) < minCapacity_)
   190             return true;
   191         return changeCapacity(capacity_ >> 1);
   192     }
   193     
   194     bool grow() {
   195         if (capacity_ >= kMaxCapacity) {
   196             this->reportAllocationOverflow();
   197             return false;
   198         }
   199         return changeCapacity(capacity_ << 1);
   200     }
   201 
   202     bool changeCapacity(uint32 newCapacity) {
   203         assert(newCapacity <= kMaxCapacity);
   204 
   205         Entry *newTable = createTable(newCapacity);
   206         if (!newTable)
   207             return false;
   208 
   209         Entry *oldTable = table_;
   210         uint32 oldCapacity = capacity_;
   211 
   212         table_ = newTable;
   213         capacity_ = newCapacity;
   214         ndeleted_ = 0;
   215 
   216         for (uint32 i = 0; i < oldCapacity; i++) {
   217             Entry &oldEntry = oldTable[i];
   218             if (oldEntry.isLive()) {
   219                 Insert p = insertUnique(oldEntry.hash());
   220                 p.entry().setHash(p.hash());
   221                 p.entry().payload() = oldEntry.payload();
   222             }
   223             oldEntry.destruct();
   224         }
   225         this->free(oldTable);
   226 
   227         return true;
   228     }
   229 
   230     // For use when the key is known to be unique.
   231     Insert insertUnique(uint32 hash) {
   232         Probulator probulator(hash, capacity_);
   233 
   234         Entry *e = &table_[probulator.entry()];
   235         for (;;) {
   236             if (e->free() || e->removed())
   237                 break;
   238             e = &table_[probulator.next()];
   239         }
   240 
   241         return Insert(e, hash);
   242     }
   243 
   244     template <typename Key>
   245     Result lookup(const Key &key) {
   246         uint32 hash = computeHash(key);
   247         Probulator probulator(hash, capacity_);
   248 
   249         Entry *e = &table_[probulator.entry()];
   250         for (;;) {
   251             if (e->free())
   252                 break;
   253             if (e->isLive() &&
   254                 e->sameHash(hash) &&
   255                 HashPolicy::matches(key, e->payload()))
   256             {
   257                 return Result(e);
   258             }
   259             e = &table_[probulator.next()];
   260         }
   261 
   262         return Result(e);
   263     }
   264 
   265     template <typename Key>
   266     Insert lookupForAdd(const Key &key) {
   267         uint32 hash = computeHash(key);
   268         Probulator probulator(hash, capacity_);
   269 
   270         Entry *e = &table_[probulator.entry()];
   271         for (;;) {
   272             if (!e->isLive())
   273                 break;
   274             if (e->sameHash(hash) && HashPolicy::matches(key, e->payload()))
   275                 break;
   276             e = &table_[probulator.next()];
   277         }
   278 
   279         return Insert(e, hash);
   280     }
   281 
   282     bool add(Insert &i) {
   283         assert(!i.found());
   284 
   285         // If the entry is deleted, just re-use the slot.
   286         if (i.entry().removed()) {
   287             ndeleted_--;
   288             return true;
   289         }
   290 
   291         // Otherwise, see if we're at max capacity.
   292         if (nelements_ == kMaxCapacity) {
   293             this->reportAllocationOverflow();
   294             return false;
   295         }
   296 
   297         // Check if the table is over or underloaded. The table is always at
   298         // least 25% free, so this check is enough to guarantee one free slot.
   299         // (Without one free slot, insertion search could infinite loop.)
   300         uint32 oldCapacity = capacity_;
   301         if (!checkDensity())
   302             return false;
   303 
   304         // If the table changed size, we need to find a new insertion point.
   305         // Note that a removed entry is impossible: either we caught it above,
   306         // or we just resized and no entries are removed.
   307         if (capacity_ != oldCapacity)
   308             i = insertUnique(i.hash());
   309 
   310         i.entry().setHash(i.hash());
   311         nelements_++;
   312 
   313         return true;
   314     }
   315 
   316     void remove(Entry &e) {
   317         assert(e.isLive());
   318         e.setRemoved();
   319         ndeleted_++;
   320         nelements_--;
   321     }
   322 
   323   public:
   324     HashTable(AllocPolicy ap)
   325       : AllocPolicy(ap),
   326         capacity_(0),
   327         nelements_(0),
   328         ndeleted_(0),
   329         table_(NULL),
   330         minCapacity_(kMinCapacity)
   331     {
   332     }
   333 
   334     ~HashTable()
   335     {
   336         for (uint32 i = 0; i < capacity_; i++)
   337             table_[i].destruct();
   338         this->free(table_);
   339     }
   340 
   341     bool init(uint32 capacity = 0) {
   342         if (capacity < kMinCapacity) {
   343             capacity = kMinCapacity;
   344         } else if (capacity > kMaxCapacity) {
   345             this->reportAllocationOverflow();
   346             return false;
   347         }
   348 
   349         minCapacity_ = capacity;
   350 
   351         assert(IsPowerOfTwo(capacity));
   352         capacity_ = capacity;
   353 
   354         table_ = createTable(capacity_);
   355         if (!table_)
   356             return false;
   357 
   358         return true;
   359     }
   360 
   361     template <typename Key>
   362     Result find(const Key &key) {
   363         return lookup(key);
   364     }
   365 
   366     template <typename Key>
   367     Insert findForAdd(const Key &key) {
   368         return lookupForAdd(key);
   369     }
   370 
   371     bool add(Insert &i, const Payload &payload) {
   372         if (!add(i))
   373             return false;
   374         *i = payload;
   375         return true;
   376     }
   377 
   378     bool checkDensity() {
   379         if (underloaded())
   380             return shrink(); 
   381         if (overloaded())
   382             return grow();
   383         return true;
   384     }
   385 
   386   public:
   387     class iterator
   388     {
   389         HashTable *table_;
   390         Entry *i_;
   391         Entry *end_;
   392 
   393       public:
   394         iterator(HashTable *table)
   395           : table_(table),
   396             i_(table->table_),
   397             end_(table->table_ + table->capacity_)
   398         {
   399             while (i_ < end_ && !i_->isLive())
   400                 i_++;
   401         }
   402 
   403         bool empty() const {
   404             return i_ == end_;
   405         }
   406 
   407         void erase() {
   408             assert(!empty());
   409             table_->remove(*i_);
   410         }
   411 
   412         const Payload &operator *() const {
   413             return i_->payload();
   414         }
   415 
   416         void next() {
   417             do {
   418                 i_++;
   419             } while (i_ < end_ && !i_->isLive());
   420         }
   421     };
   422 };
   423 
   424 
   425 
   426 }
   427 
   428 #endif // _INCLUDE_KEIMA_HASHTABLE_H_