1 | // tMap.h  |
2 | //  |
3 | // A map class (dictionary) that keeps track of keys and associated values, both of which may be any type. The  |
4 | // requirements are: The key type must be copyable, comparable, and convertable to a uint32. The value type must be  |
5 | // copyable. tMap is implemented as a hash-table with lists to resolve collisions and has expected O(1) running  |
6 | // time for insertions and value retrievals. The hash table automatically grows when a threshold percentage of the hash  |
7 | // table is used (defaulting to 60%). Keys are unique -- the last value assigned to a key is the one stored in the tMap.  |
8 | //  |
9 | // You may iterate through a tMap to retrieve all keys and values. Range-based for loops are supported. Note that this  |
10 | // is slightly less efficient than iterating through a tList though, as empty nodes in the hash table are visited.  |
11 | //  |
12 | // Copyright (c) 2020 Tristan Grimmer.  |
13 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
14 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
15 | //  |
16 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
17 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
18 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
19 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
20 | // PERFORMANCE OF THIS SOFTWARE.  |
21 |   |
22 | #pragma once  |
23 | #include "Foundation/tAssert.h"  |
24 | #include "Foundation/tPlatform.h"  |
25 | #include "Foundation/tFundamentals.h"  |
26 | #include "Foundation/tList.h"  |
27 |   |
28 |   |
29 | template<typename K, typename V> class tMap  |
30 | {  |
31 | public:  |
32 | // Set therekey percent > 1.0f to prevent all rekeying / resizing.  |
33 | tMap(int initialLog2Size = 8, float rekeyPercent = 0.6f);  |
34 | ~tMap();  |
35 |   |
36 | // These are fast with expected O(1) running time.  |
37 | V& GetInsert(const K&);  |
38 | V& operator[](const K&);  |
39 | bool Remove(const K&);  |
40 | int GetNumItems() const { return NumItems; }  |
41 |   |
42 | // These are mostly for debugging or checking performance of the hash table.  |
43 | int GetHashTableSize() const { return HashTableSize; }  |
44 | int GetHashTableEntryCount() const { return HashTableEntryCount; }  |
45 | int GetHashTableCollisions() const { return NumItems - HashTableEntryCount; }  |
46 | float GetHashTablePercent() const { return float(HashTableEntryCount)/float(HashTableSize); }  |
47 |   |
48 | private:  |
49 | struct Pair  |
50 | {  |
51 | Pair(const Pair& pair) : Key(pair.Key), Value(pair.Value) { }  |
52 | Pair(const K& key) : Key(key), Value() { }  |
53 | K Key; V Value;  |
54 | };  |
55 | struct HashTableItem  |
56 | {  |
57 | tItList<Pair> Pairs;  |
58 | };  |
59 | void Rekey(int newSize);  |
60 | int NumItems;  |
61 | int HashTableSize;  |
62 | int HashTableEntryCount;  |
63 | HashTableItem* HashTable;  |
64 | float RekeyPercent;  |
65 |   |
66 | public:  |
67 | // If needed you can use this iterator to query every key and/or value in the tMap. Note that the keys and values  |
68 | // are stored unordered. Further, since not all hash table entries will have valid data, it is slightly less  |
69 | // efficient to iterate through a tMap vs interating through, for example, a tItList, since empties must be skipped.  |
70 | class Iter  |
71 | {  |
72 | public:  |
73 | Iter() : Map(nullptr), TableIndex(-1), PairIter() { }  |
74 | Iter(const Iter& src) : Map(src.Map), TableIndex(src.TableIndex), PairIter(src.PairIter) { }  |
75 |   |
76 | bool IsValid() const { return PairIter.IsValid(); }  |
77 | void Clear() { Map = nullptr; TableIndex = -1; PairIter.Clear(); }  |
78 | void Next();  |
79 | V& Value() const { return PairIter->Value; }  |
80 | K& Key() const { return PairIter->Key; }  |
81 |   |
82 | Iter& operator*() { return *this; }  |
83 | Iter* operator->() const { return this; }  |
84 | operator bool() const { return PairIter.IsValid(); }  |
85 | Iter& operator=(const Iter& i) { if (this != &i) { Map = i.Map; TableIndex = i.TableIndex; PairIter = i.PairIter; } return *this; }  |
86 |   |
87 | // Use ++iter instead of iter++ when possible. Since the hash table is unordered, there's no point in offering  |
88 | // both forward and backwards iteration. Therefore there's only First, Next, operator++ etc.  |
89 | const Iter operator++(int) { Iter curr(*this); Next(); return curr; }  |
90 | const Iter operator++() { Next(); return *this; }  |
91 | const Iter operator+(int offset) const { Iter i = *this; while (offset--) i.Next(); return i; }  |
92 | Iter& operator+=(int offset) { tAssert(offset >= 0); while (offset--) Next(); return *this; }  |
93 | bool operator==(const Iter& i) const { return (Map == i.Map) && (TableIndex == i.TableIndex) && (PairIter == i.PairIter); }  |
94 | bool operator!=(const Iter& i) const { return (Map != i.Map) || (TableIndex != i.TableIndex) || (PairIter != i.PairIter); }  |
95 |   |
96 | private:  |
97 | friend class tMap;  |
98 | Iter(const tMap<K,V>* map, int tableIndex, typename tItList<Pair>::Iter iter) : Map(map), TableIndex(tableIndex), PairIter(iter) { }  |
99 | const tMap<K,V>* Map;  |
100 | int TableIndex;  |
101 | typename tItList<Pair>::Iter PairIter;  |
102 | };  |
103 |   |
104 | public:  |
105 | Iter First() const;  |
106 | Iter begin() const /* For range-based iteration supported by C++11. */ { return First(); }  |
107 | Iter end() const /* For range-based iteration supported by C++11. */ { return Iter(this, -1, typename tItList<Pair>::Iter()); }  |
108 | };  |
109 |   |
110 |   |
111 | // Implementation below this line.  |
112 |   |
113 |   |
114 | template<typename K, typename V> inline tMap<K,V>::tMap(int initialLog2Size, float rekeyPercent)  |
115 | {  |
116 | NumItems = 0;  |
117 |   |
118 | tAssert(initialLog2Size >= 0);  |
119 | HashTableSize = 1 << initialLog2Size;  |
120 |   |
121 | tAssert(rekeyPercent >= 0.0f);  |
122 | RekeyPercent = rekeyPercent;  |
123 |   |
124 | HashTableEntryCount = 0;  |
125 | HashTable = new HashTableItem[HashTableSize];  |
126 | }  |
127 |   |
128 |   |
129 | template<typename K, typename V> inline tMap<K,V>::~tMap()  |
130 | {  |
131 | delete[] HashTable;  |
132 | }  |
133 |   |
134 |   |
135 | template<typename K, typename V> inline V& tMap<K,V>::GetInsert(const K& key)  |
136 | {  |
137 | // Do we need to grow the hash table?  |
138 | if (GetHashTablePercent() >= RekeyPercent)  |
139 | Rekey(2*HashTableSize);  |
140 |   |
141 | uint32 hash = uint32(key);  |
142 | int hashBits = tMath::tLog2(HashTableSize);  |
143 | hash = hash & (0xFFFFFFFF >> (32-hashBits));  |
144 | tAssert(hash < HashTableSize);  |
145 |   |
146 | HashTableItem& item = HashTable[hash];  |
147 | for (Pair& pair : item.Pairs)  |
148 | {  |
149 | if (pair.Key == key)  |
150 | return pair.Value;  |
151 | }  |
152 |   |
153 | if (item.Pairs.IsEmpty())  |
154 | HashTableEntryCount++;  |
155 | NumItems++;  |
156 | return item.Pairs.Append(new Pair(key))->Value;  |
157 | }  |
158 |   |
159 |   |
160 | template<typename K, typename V> inline V& tMap<K,V>::operator[](const K& key)  |
161 | {  |
162 | return GetInsert(key);  |
163 | }  |
164 |   |
165 |   |
166 | template<typename K, typename V> inline bool tMap<K,V>::Remove(const K& key)  |
167 | {  |
168 | uint32 hash = uint32(key);  |
169 | int hashBits = tMath::tLog2(HashTableSize);  |
170 | hash = hash & (0xFFFFFFFF >> (32-hashBits));  |
171 | tAssert(hash < HashTableSize);  |
172 |   |
173 | HashTableItem& item = HashTable[hash];  |
174 |   |
175 | for (auto iter = item.Pairs.First(); iter; iter++)  |
176 | {  |
177 | if (iter->Key == key)  |
178 | {  |
179 | delete item.Pairs.Remove(iter);  |
180 | if (item.Pairs.IsEmpty())  |
181 | HashTableEntryCount--;  |
182 | NumItems--;  |
183 | return true;  |
184 | }  |
185 | }  |
186 |   |
187 | return false;  |
188 | }  |
189 |   |
190 |   |
191 | template<typename K, typename V> inline void tMap<K,V>::Rekey(int newSize)  |
192 | {  |
193 | tAssert((newSize > HashTableSize) && tMath::tIsPower2(newSize));  |
194 | HashTableItem* newTable = new HashTableItem[newSize];  |
195 |   |
196 | // Loop throught existing keys and rekey them into the new table  |
197 | for (int i = 0; i < HashTableSize; i++)  |
198 | {  |
199 | for (Pair& pair : HashTable[i].Pairs)  |
200 | {  |
201 | uint32 hashNew = uint32(pair.Key);  |
202 | int hashBitsNew = tMath::tLog2(newSize);  |
203 | hashNew = hashNew & (0xFFFFFFFF >> (32-hashBitsNew));  |
204 |   |
205 | tAssert(hashNew < newSize);  |
206 | newTable[hashNew].Pairs.Append(new Pair(pair));  |
207 | }  |
208 | HashTable[i].Pairs.Clear();  |
209 | }  |
210 |   |
211 | HashTableSize = newSize;  |
212 | delete[] HashTable;  |
213 | HashTable = newTable;  |
214 | }  |
215 |   |
216 |   |
217 | template<typename K, typename V> inline void tMap<K,V>::Iter::Next()  |
218 | {  |
219 | // If we can just advance the PairIter, we're done.  |
220 | PairIter++;  |
221 | if (PairIter.IsValid())  |
222 | return;  |
223 |   |
224 | // Try next hash table entries until we either find a non-empty one or reach the end of the table.  |
225 | while (++TableIndex < Map->HashTableSize)  |
226 | {  |
227 | HashTableItem& item = Map->HashTable[TableIndex];  |
228 | if (!item.Pairs.IsEmpty())  |
229 | {  |
230 | PairIter = item.Pairs.First();  |
231 | return;  |
232 | }  |
233 | };  |
234 |   |
235 | // It is vital to have 'this' be the same as end() here, as ranged-based for loops must return false when  |
236 | // comparing the last Next() with end() using != operator. This is why must set the index to -1. To make  |
237 | // sure it matches end().  |
238 | TableIndex = -1;  |
239 | PairIter = typename tItList<Pair>::Iter();   |
240 | }  |
241 |   |
242 |   |
243 | template<typename K, typename V> inline typename tMap<K,V>::Iter tMap<K,V>::First() const  |
244 | {  |
245 | for (int tableIndex = 0; tableIndex < HashTableSize; tableIndex++)  |
246 | {  |
247 | HashTableItem& item = HashTable[tableIndex];  |
248 | if (!item.Pairs.IsEmpty())  |
249 | return Iter(this, tableIndex, item.Pairs.First());  |
250 | }  |
251 | return Iter(this, -1, typename tItList<Pair>::Iter());  |
252 | }  |
253 | |