File: HashTable.h

package info (click to toggle)
pd-vstplugin 0.6.1-1~exp1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 2,008 kB
  • sloc: cpp: 22,783; lisp: 2,860; makefile: 37; sh: 26
file content (145 lines) | stat: -rw-r--r-- 4,213 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#pragma once

#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <vector>

namespace vst {

// Custom open addressing hashtable that supports custom view types
// for keys to avoid expensive conversions in find() method.
// A typical use case would be std::string_view for std::string keys.
// Key must be constructible from KeyView, KeyView must be convertable
// to Key and both types must be comparable with each other.
template<typename Key, typename Value, typename KeyView = Key>
class HashTable {
public:
    HashTable() { array_.resize(initialCapacity); };

    template<typename U>
    bool insert(const KeyView& key, U&& value) {
        return insert(Key{key}, std::forward<U>(value));
    }

    template<typename U>
    bool insert(Key&& key, U&& value);

    const Value* find(const KeyView& key) const;

    template<typename U>
    Value findOr(const KeyView& key, U&& value) const {
        if (auto result = find(key)) {
            return *result;
        } else {
            return std::forward<U>(value);
        }
    }

    size_t size() const {
        return count_;
    }

    void clear() {
        for (auto& e : array_) {
            e = Entry{};
        }
        count_ = 0;
    }
private:
    static constexpr size_t initialCapacity = 8;

    using HashType = uint32_t;
    static constexpr HashType flag = (HashType)1 << (sizeof(HashType) * CHAR_BIT - 1);

    static HashType makeHash(const KeyView& key) {
        HashType hash = std::hash<KeyView>{}(key);
        return hash & ~flag;
    }

    void rehash() {
        auto newSize = array_.size() * 2;
        auto oldArray = std::move(array_);
        array_ = std::vector<Entry>(newSize);
        for (auto& e : oldArray) {
            if (!e.empty()) {
                auto success = insert(std::move(e.key_), std::move(e.value_));
                assert(success);
            }
        }
    }

    struct Entry {
        Key key_{};
        HashType hash_{}; // highest bit is reserved (1 = slot is occupied)
        Value value_{};

        HashType hash() const {
            return hash_ & ~flag;
        }

        bool empty() const {
            return !(hash_ & flag);
        }
    };

    std::vector<Entry> array_;
    size_t count_ = 0;
};

template<typename Key, typename Value, typename KeyView>
template<typename U>
inline bool HashTable<Key, Value, KeyView>::insert(Key&& key, U&& value) {
    // rehash if load factor exceeds 0.5
    if (count_ >= array_.size() / 2) {
        rehash();
    }

    const auto hash = makeHash(key);
    // NB: array size is always power of two!
    const auto mask = array_.size() - 1;
    for (size_t index = hash;; ++index) {
        index = index & mask;
        if (array_[index].empty()) {
            // found free slot
            array_[index].key_ = std::move(key);
            array_[index].hash_ = hash | flag; // mark as occupied!
            array_[index].value_ = std::forward<U>(value);

            count_++;

            return true;
        } else {
            // check if key already exists!
            if (hash == array_[index].hash() && key == array_[index].key_) {
                return false;
            }
        }
    }
    // unreachable (the hashtable should always contain empty slots)
    assert(false);
    return false;
}

template<typename Key, typename Value, typename KeyView>
const Value* HashTable<Key, Value, KeyView>::find(const KeyView& key) const {
    const auto hash = makeHash(key);
    // NB: array size is always power of two!
    const auto mask = array_.size() - 1;
    for (size_t index = hash;; ++index) {
        index = index & mask;
        if (array_[index].empty()) {
            return nullptr; // hit empty slot
        } else {
            if (hash == array_[index].hash() && key == array_[index].key_) {
                return &array_[index].value_; // found match!
            }
        }
    }
    // unreachable (the hashtable should always contain empty slots)
    assert(false);
    return nullptr;
}

} // namespace vst