jitcraft/HashTable.h
author David Anderson <dvander@alliedmods.net>
Tue Jun 26 00:35:15 2012 -0700 (2012-06-26)
changeset 2 46fc53b8fd2f
parent 0 c455ebb9a5dc
child 54 8e22792a3921
permissions -rw-r--r--
Added support for enums.
dvander@0
     1
// vim: set ts=4 sw=4 tw=99 et:
dvander@0
     2
//
dvander@0
     3
//  
dvander@0
     4
// (C) Copyright 2010-2011 AlliedModders LLC
dvander@0
     5
//
dvander@0
     6
// Initial Author: David Anderson
dvander@0
     7
// Contributors:
dvander@0
     8
//
dvander@0
     9
//
dvander@0
    10
dvander@0
    11
#ifndef _INCLUDE_KEIMA_HASHTABLE_H_
dvander@0
    12
#define _INCLUDE_KEIMA_HASHTABLE_H_
dvander@0
    13
dvander@0
    14
#include <new>
dvander@0
    15
#include <limits.h>
dvander@0
    16
#include "Object.h"
dvander@0
    17
#include "vm/Array.h"
dvander@0
    18
#include "Environment.h"
dvander@0
    19
dvander@0
    20
namespace ke {
dvander@0
    21
dvander@0
    22
template <typename T>
dvander@0
    23
class HashTableEntry
dvander@0
    24
{
dvander@0
    25
    uint32 hash_;
dvander@0
    26
    T t_;
dvander@0
    27
dvander@0
    28
  public:
dvander@0
    29
    static const uint32 kFreeHash = 0;
dvander@0
    30
    static const uint32 kRemovedHash = 1;
dvander@0
    31
dvander@0
    32
  public:
dvander@0
    33
    void setHash(uint32 hash) {
dvander@0
    34
        hash_ = hash;
dvander@0
    35
    }
dvander@0
    36
    uint32 hash() const {
dvander@0
    37
        return hash_;
dvander@0
    38
    }
dvander@0
    39
    void setRemoved() {
dvander@0
    40
        hash_ = kRemovedHash;
dvander@0
    41
        destruct();
dvander@0
    42
    }
dvander@0
    43
    void construct() {
dvander@0
    44
        new (&t_) T();
dvander@0
    45
    }
dvander@0
    46
    void destruct() {
dvander@0
    47
        t_.~T();
dvander@0
    48
    }
dvander@0
    49
    bool removed() const {
dvander@0
    50
        return hash_ == kRemovedHash;
dvander@0
    51
    }
dvander@0
    52
    bool free() const {
dvander@0
    53
        return hash_ == kFreeHash;
dvander@0
    54
    }
dvander@0
    55
    bool isLive() const {
dvander@0
    56
        return hash_ > kRemovedHash;
dvander@0
    57
    }
dvander@0
    58
    T &payload() {
dvander@0
    59
        return t_;
dvander@0
    60
    }
dvander@0
    61
    bool sameHash(uint32 hash) const {
dvander@0
    62
        return hash_ == hash;
dvander@0
    63
    }
dvander@0
    64
};
dvander@0
    65
dvander@0
    66
template <typename HashPolicy, typename AllocPolicy>
dvander@0
    67
class HashTable : public AllocPolicy
dvander@0
    68
{
dvander@0
    69
    typedef typename HashPolicy::Payload Payload;
dvander@0
    70
    typedef HashTableEntry<Payload> Entry;
dvander@0
    71
dvander@0
    72
    uint32 capacity_;
dvander@0
    73
    uint32 nelements_;
dvander@0
    74
    uint32 ndeleted_;
dvander@0
    75
    Entry *table_;
dvander@0
    76
dvander@0
    77
  private:
dvander@0
    78
    static const uint32 kMinCapacity = 16;
dvander@0
    79
    static const uint32 kMaxCapacity = INT_MAX / sizeof(Entry);
dvander@0
    80
dvander@0
    81
    template <typename Key>
dvander@0
    82
    uint32 computeHash(const Key &key) {
dvander@0
    83
        // Multiply by golden ratio.
dvander@0
    84
        uint32 hash = HashPolicy::hash(key) * 0x9E3779B9;
dvander@0
    85
        if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash)
dvander@0
    86
            hash += 2;
dvander@0
    87
        return hash;
dvander@0
    88
    }
dvander@0
    89
dvander@0
    90
    Entry *createTable(uint32 capacity) {
dvander@0
    91
        KE_ASSERT(capacity <= kMaxCapacity);
dvander@0
    92
dvander@0
    93
        Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry));
dvander@0
    94
        if (!table)
dvander@0
    95
            return NULL;
dvander@0
    96
dvander@0
    97
        for (size_t i = 0; i < capacity; i++)
dvander@0
    98
            table[i].construct();
dvander@0
    99
dvander@0
   100
        return table;
dvander@0
   101
    }
dvander@0
   102
dvander@0
   103
  public:
dvander@0
   104
    class Result
dvander@0
   105
    {
dvander@0
   106
        friend class HashTable;
dvander@0
   107
dvander@0
   108
        Entry *entry_;
dvander@0
   109
dvander@0
   110
        Entry &entry() {
dvander@0
   111
            return *entry_;
dvander@0
   112
        }
dvander@0
   113
dvander@0
   114
      public:
dvander@0
   115
        Result(Entry *entry)
dvander@0
   116
          : entry_(entry)
dvander@0
   117
        { }
dvander@0
   118
dvander@0
   119
        Payload * operator ->() {
dvander@0
   120
            return entry_->payload();
dvander@0
   121
        }
dvander@0
   122
        Payload & operator *() {
dvander@0
   123
            return entry_->payload();
dvander@0
   124
        }
dvander@0
   125
dvander@0
   126
        bool found() const {
dvander@0
   127
            return entry_->isLive();
dvander@0
   128
        }
dvander@0
   129
    };
dvander@0
   130
dvander@0
   131
    class Insert : public Result
dvander@0
   132
    {
dvander@0
   133
        uint32 hash_;
dvander@0
   134
dvander@0
   135
      public:
dvander@0
   136
        Insert(Entry *entry, uint32 hash)
dvander@0
   137
          : Result(entry),
dvander@0
   138
            hash_(hash)
dvander@0
   139
        {
dvander@0
   140
        }
dvander@0
   141
dvander@0
   142
        uint32 hash() const {
dvander@0
   143
            return hash_;
dvander@0
   144
        }
dvander@0
   145
    };
dvander@0
   146
dvander@0
   147
  private:
dvander@0
   148
    class Probulator {
dvander@0
   149
        uint32 hash_;
dvander@0
   150
        uint32 capacity_;
dvander@0
   151
dvander@0
   152
      public:
dvander@0
   153
        Probulator(uint32 hash, uint32 capacity)
dvander@0
   154
          : hash_(hash),
dvander@0
   155
            capacity_(capacity)
dvander@0
   156
        {
dvander@0
   157
            KE_ASSERT(IsPowerOfTwo(capacity_)); 
dvander@0
   158
        }
dvander@0
   159
dvander@0
   160
        uint32 entry() const {
dvander@0
   161
            return hash_ & (capacity_ - 1);
dvander@0
   162
        }
dvander@0
   163
        uint32 next() {
dvander@0
   164
            hash_++;
dvander@0
   165
            return entry();
dvander@0
   166
        }
dvander@0
   167
    };
dvander@0
   168
dvander@0
   169
    bool underloaded() const {
dvander@0
   170
        // Check if the table is underloaded: < 25% entries used.
dvander@0
   171
        return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4);
dvander@0
   172
    }
dvander@0
   173
    bool overloaded() const {
dvander@0
   174
        // Grow if the table is overloaded: > 75% entries used.
dvander@0
   175
        return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4));
dvander@0
   176
    }
dvander@0
   177
dvander@0
   178
    bool shrink() {
dvander@0
   179
        return changeCapacity(capacity_ >> 1);
dvander@0
   180
    }
dvander@0
   181
    
dvander@0
   182
    bool grow() {
dvander@0
   183
        KE_ASSERT(IsPowerOfTwo(kMaxCapacity));
dvander@0
   184
        if (capacity_ >= kMaxCapacity) {
dvander@0
   185
            this->reportAllocationOverflow();
dvander@0
   186
            return false;
dvander@0
   187
        }
dvander@0
   188
        return changeCapacity(capacity_ << 1);
dvander@0
   189
    }
dvander@0
   190
dvander@0
   191
    bool checkDensity() {
dvander@0
   192
        if (underloaded())
dvander@0
   193
            return shrink(); 
dvander@0
   194
        if (overloaded())
dvander@0
   195
            return grow();
dvander@0
   196
        return true;
dvander@0
   197
    }
dvander@0
   198
dvander@0
   199
    bool changeCapacity(uint32 newCapacity) {
dvander@0
   200
        KE_ASSERT(newCapacity <= kMaxCapacity);
dvander@0
   201
dvander@0
   202
        Entry *newTable = createTable(newCapacity);
dvander@0
   203
        if (!newTable)
dvander@0
   204
            return false;
dvander@0
   205
dvander@0
   206
        Entry *oldTable = table_;
dvander@0
   207
        uint32 oldCapacity = capacity_;
dvander@0
   208
dvander@0
   209
        table_ = newTable;
dvander@0
   210
        capacity_ = newCapacity;
dvander@0
   211
        ndeleted_ = 0;
dvander@0
   212
dvander@0
   213
        for (uint32 i = 0; i < oldCapacity; i++) {
dvander@0
   214
            Entry &oldEntry = oldTable[i];
dvander@0
   215
            if (oldEntry.isLive()) {
dvander@0
   216
                Insert p = insertUnique(oldEntry.hash());
dvander@0
   217
                p.entry().setHash(p.hash());
dvander@0
   218
                p.entry().payload() = oldEntry.payload();
dvander@0
   219
            }
dvander@0
   220
            oldEntry.destruct();
dvander@0
   221
        }
dvander@0
   222
        this->free(table_);
dvander@0
   223
dvander@0
   224
        return true;
dvander@0
   225
    }
dvander@0
   226
dvander@0
   227
    // For use when the key is known to be unique.
dvander@0
   228
    Insert insertUnique(uint32 hash) {
dvander@0
   229
        Probulator probulator(hash, capacity_);
dvander@0
   230
dvander@0
   231
        Entry *e = &table_[probulator.entry()];
dvander@0
   232
        for (;;) {
dvander@0
   233
            if (e->free() || e->removed())
dvander@0
   234
                break;
dvander@0
   235
            e = &table_[probulator.next()];
dvander@0
   236
        }
dvander@0
   237
dvander@0
   238
        return Insert(e, hash);
dvander@0
   239
    }
dvander@0
   240
dvander@0
   241
    template <typename Key>
dvander@0
   242
    Result lookup(const Key &key) {
dvander@0
   243
        uint32 hash = computeHash(key);
dvander@0
   244
        Probulator probulator(hash, capacity_);
dvander@0
   245
dvander@0
   246
        Entry *e = &table_[probulator.entry()];
dvander@0
   247
        for (;;) {
dvander@0
   248
            if (e->free())
dvander@0
   249
                break;
dvander@0
   250
            if (e->isLive() &&
dvander@0
   251
                e->sameHash(hash) &&
dvander@0
   252
                HashPolicy::matches(key, e->payload()))
dvander@0
   253
            {
dvander@0
   254
                return Result(e);
dvander@0
   255
            }
dvander@0
   256
            e = &table_[probulator.next()];
dvander@0
   257
        }
dvander@0
   258
dvander@0
   259
        return Result(e);
dvander@0
   260
    }
dvander@0
   261
dvander@0
   262
    template <typename Key>
dvander@0
   263
    Insert lookupForAdd(const Key &key) {
dvander@0
   264
        uint32 hash = computeHash(key);
dvander@0
   265
        Probulator probulator(hash, capacity_);
dvander@0
   266
dvander@0
   267
        Entry *e = &table_[probulator.entry()];
dvander@0
   268
        for (;;) {
dvander@0
   269
            if (!e->isLive())
dvander@0
   270
                break;
dvander@0
   271
            if (e->sameHash(hash) && HashPolicy::matches(key, e->payload()))
dvander@0
   272
                break;
dvander@0
   273
            e = &table_[probulator.next()];
dvander@0
   274
        }
dvander@0
   275
dvander@0
   276
        return Insert(e, hash);
dvander@0
   277
    }
dvander@0
   278
dvander@0
   279
    bool add(Insert &i) {
dvander@0
   280
        KE_ASSERT(!i.found());
dvander@0
   281
dvander@0
   282
        // If the entry is deleted, just re-use the slot.
dvander@0
   283
        if (i.entry().removed()) {
dvander@0
   284
            ndeleted_--;
dvander@0
   285
            return true;
dvander@0
   286
        }
dvander@0
   287
dvander@0
   288
        // Otherwise, see if we're at max capacity.
dvander@0
   289
        if (nelements_ == kMaxCapacity) {
dvander@0
   290
            this->reportAllocationOverflow();
dvander@0
   291
            return false;
dvander@0
   292
        }
dvander@0
   293
dvander@0
   294
        // Check if the table is over or underloaded. The table is always at
dvander@0
   295
        // least 25% free, so this check is enough to guarantee one free slot.
dvander@0
   296
        // (Without one free slot, insertion search could infinite loop.)
dvander@0
   297
        uint32 oldCapacity = capacity_;
dvander@0
   298
        if (!checkDensity())
dvander@0
   299
            return false;
dvander@0
   300
dvander@0
   301
        // If the table changed size, we need to find a new insertion point.
dvander@0
   302
        // Note that a removed entry is impossible: either we caught it above,
dvander@0
   303
        // or we just resized and no entries are removed.
dvander@0
   304
        if (capacity_ != oldCapacity)
dvander@0
   305
            i = insertUnique(i.hash());
dvander@0
   306
dvander@0
   307
        i.entry().setHash(i.hash());
dvander@0
   308
        nelements_++;
dvander@0
   309
dvander@0
   310
        return true;
dvander@0
   311
    }
dvander@0
   312
dvander@0
   313
  public:
dvander@0
   314
    HashTable(AllocPolicy ap)
dvander@0
   315
      : AllocPolicy(ap),
dvander@0
   316
        capacity_(0),
dvander@0
   317
        nelements_(0),
dvander@0
   318
        ndeleted_(0),
dvander@0
   319
        table_(NULL)
dvander@0
   320
    {
dvander@0
   321
    }
dvander@0
   322
dvander@0
   323
    ~HashTable()
dvander@0
   324
    {
dvander@0
   325
        for (uint32 i = 0; i < capacity_; i++)
dvander@0
   326
            table_[i].destruct();
dvander@0
   327
        this->free(table_);
dvander@0
   328
    }
dvander@0
   329
dvander@0
   330
    bool init(uint32 capacity = 0) {
dvander@0
   331
        if (capacity < kMinCapacity)
dvander@0
   332
            capacity = kMinCapacity;
dvander@0
   333
        else if (capacity > kMaxCapacity)
dvander@0
   334
            capacity = kMaxCapacity;
dvander@0
   335
dvander@0
   336
        KE_ASSERT(IsPowerOfTwo(capacity));
dvander@0
   337
        capacity_ = capacity;
dvander@0
   338
dvander@0
   339
        table_ = createTable(capacity_);
dvander@0
   340
        if (!table_)
dvander@0
   341
            return false;
dvander@0
   342
dvander@0
   343
        return true;
dvander@0
   344
    }
dvander@0
   345
dvander@0
   346
    template <typename Key>
dvander@0
   347
    Result find(const Key &key) {
dvander@0
   348
        return lookup(key);
dvander@0
   349
    }
dvander@0
   350
dvander@0
   351
    template <typename Key>
dvander@0
   352
    Insert findForAdd(const Key &key) {
dvander@0
   353
        return lookupForAdd(key);
dvander@0
   354
    }
dvander@0
   355
dvander@0
   356
    bool add(Insert &i, const Payload &payload) {
dvander@0
   357
        if (!add(i))
dvander@0
   358
            return false;
dvander@0
   359
        *i = payload;
dvander@0
   360
        return true;
dvander@0
   361
    }
dvander@0
   362
};
dvander@0
   363
dvander@0
   364
}
dvander@0
   365
dvander@0
   366
#endif // _INCLUDE_KEIMA_HASHTABLE_H_
dvander@0
   367