DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
Dictionary.h
Go to the documentation of this file.
1#pragma once
2
3#include "Common.h"
4#include "System/Object.h"
5#include "System/Array.h"
8#include "System/HashHelper.h"
9#include <new>
10#include <utility>
11
12namespace DotNetDupe {
13 namespace System {
14 namespace Collections {
15 namespace Generic {
16
21 template <typename TKey, typename TValue>
22 struct KeyValuePair {
23 TKey Key;
24 TValue Value;
25
28
32 KeyValuePair(TKey k, TValue v) : Key(k), Value(v) {}
33 };
34
37 struct HashHelpers {
39 inline static const int s_primes[] = {
40 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
41 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
42 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
43 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
44 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
45 };
46
50 static int GetPrime(int min) {
51 for (int i = 0; i < (int)(sizeof(s_primes) / sizeof(s_primes[0])); ++i) {
52 if (s_primes[i] >= min) return s_primes[i];
53 }
54 return min;
55 }
56 };
57
65 template <typename TKey, typename TValue>
66 class Dictionary : public Object {
67 private:
68 struct Entry {
69 int hashCode;
70 int next;
71 TKey key;
72 TValue value;
73
74 Entry() : hashCode(-1), next(-1) {}
75 Entry(int h, int n, const TKey& k, const TValue& v) : hashCode(h), next(n), key(k), value(v) {}
76 Entry(const Entry& other) : hashCode(other.hashCode), next(other.next), key(other.key), value(other.value) {}
77 Entry(Entry&& other) noexcept : hashCode(other.hashCode), next(other.next), key(std::move(other.key)), value(std::move(other.value)) {}
78 };
79
80 int* m_pBuckets;
81 Entry* m_pEntries;
82 int m_iCount;
83 int m_iFreeList;
84 int m_iFreeCount;
85 int m_iCapacity;
86
87 void Initialize(int capacity) {
88 m_iCapacity = HashHelpers::GetPrime(capacity);
89 m_pBuckets = (int*)AllocateCollectionBuffer(m_iCapacity * sizeof(int));
90 for (int i = 0; i < m_iCapacity; i++) m_pBuckets[i] = -1;
91 m_pEntries = (Entry*)AllocateCollectionBuffer(m_iCapacity * sizeof(Entry));
92 m_iFreeList = -1;
93 m_iFreeCount = 0;
94 m_iCount = 0;
95 }
96
97 void FreeBuffer() {
98 if (m_pBuckets) FreeCollectionBuffer(m_pBuckets);
99 if (m_pEntries) {
100 for (int i = 0; i < m_iCount; ++i) {
101 if (m_pEntries[i].hashCode >= 0) m_pEntries[i].~Entry();
102 }
103 FreeCollectionBuffer(m_pEntries);
104 }
105 m_pBuckets = nullptr;
106 m_pEntries = nullptr;
107 }
108
109 void Resize() {
110 Resize(m_iCount > 0 ? m_iCount * 2 : 4);
111 }
112
113 void Resize(int newSize) {
114 int newCapacity = HashHelpers::GetPrime(newSize);
115 int* pNewBuckets = (int*)AllocateCollectionBuffer(newCapacity * sizeof(int));
116 for (int i = 0; i < newCapacity; i++) pNewBuckets[i] = -1;
117 Entry* pNewEntries = (Entry*)AllocateCollectionBuffer(newCapacity * sizeof(Entry));
118 for (int i = 0; i < m_iCount; i++) {
119 if (m_pEntries[i].hashCode >= 0) {
120 ::new ((void*)&pNewEntries[i]) Entry(std::move(m_pEntries[i]));
121 m_pEntries[i].~Entry();
122 } else {
123 ::new ((void*)&pNewEntries[i]) Entry();
124 }
125 }
126 for (int i = 0; i < m_iCount; i++) {
127 if (pNewEntries[i].hashCode >= 0) {
128 int bucket = pNewEntries[i].hashCode % newCapacity;
129 pNewEntries[i].next = pNewBuckets[bucket];
130 pNewBuckets[bucket] = i;
131 }
132 }
133 if (m_pBuckets) FreeCollectionBuffer(m_pBuckets);
134 if (m_pEntries) FreeCollectionBuffer(m_pEntries);
135 m_pBuckets = pNewBuckets;
136 m_pEntries = pNewEntries;
137 m_iCapacity = newCapacity;
138 }
139
140 int FindEntry(const TKey& key) const {
141 if (m_pBuckets != nullptr) {
142 int hashCode = HashHelper<TKey>::GetHashCode(key) & 0x7FFFFFFF;
143 for (int i = m_pBuckets[hashCode % m_iCapacity]; i >= 0; i = m_pEntries[i].next) {
144 if (m_pEntries[i].hashCode == hashCode && m_pEntries[i].key == key) return i;
145 }
146 }
147 return -1;
148 }
149
150 bool Insert(const TKey& key, const TValue& value, bool add) {
151 if (m_pBuckets == nullptr) Initialize(0);
152 int hashCode = HashHelper<TKey>::GetHashCode(key) & 0x7FFFFFFF;
153 int targetBucket = hashCode % m_iCapacity;
154
155 for (int i = m_pBuckets[targetBucket]; i >= 0; i = m_pEntries[i].next) {
156 if (m_pEntries[i].hashCode == hashCode && m_pEntries[i].key == key) {
157 if (add) throw System::ArgumentException("An item with the same key has already been added.");
158 m_pEntries[i].value = value;
159 return false;
160 }
161 }
162
163 int index;
164 if (m_iFreeCount > 0) {
165 index = m_iFreeList;
166 m_iFreeList = m_pEntries[index].next;
167 m_iFreeCount--;
168 } else {
169 if (m_iCount == m_iCapacity) {
170 Resize();
171 targetBucket = hashCode % m_iCapacity;
172 }
173 index = m_iCount;
174 m_iCount++;
175 }
176 ::new ((void*)&m_pEntries[index]) Entry(hashCode, m_pBuckets[targetBucket], key, value);
177 m_pBuckets[targetBucket] = index;
178 return true;
179 }
180
181 public:
182 Dictionary() : m_pBuckets(nullptr), m_pEntries(nullptr), m_iCount(0), m_iFreeList(-1), m_iFreeCount(0), m_iCapacity(0) {}
183
184 ~Dictionary() override {
185 FreeBuffer();
186 }
187
188 Dictionary(const Dictionary& other) : Dictionary() {
189 if (other.m_iCount > 0) {
190 Initialize(other.m_iCount);
191 for (int i = 0; i < other.m_iCount; i++) {
192 if (other.m_pEntries[i].hashCode >= 0) {
193 Add(other.m_pEntries[i].key, other.m_pEntries[i].value);
194 }
195 }
196 }
197 }
198
200 if (this != &other) {
201 Clear();
202 if (other.m_iCount > 0) {
203 Initialize(other.m_iCount);
204 for (int i = 0; i < other.m_iCount; i++) {
205 if (other.m_pEntries[i].hashCode >= 0) {
206 Add(other.m_pEntries[i].key, other.m_pEntries[i].value);
207 }
208 }
209 }
210 }
211 return *this;
212 }
213
214 Dictionary(Dictionary&& other) noexcept : m_pBuckets(other.m_pBuckets), m_pEntries(other.m_pEntries), m_iCount(other.m_iCount), m_iFreeList(other.m_iFreeList), m_iFreeCount(other.m_iFreeCount), m_iCapacity(other.m_iCapacity) {
215 other.m_pBuckets = nullptr;
216 other.m_pEntries = nullptr;
217 other.m_iCount = 0;
218 other.m_iFreeList = -1;
219 other.m_iFreeCount = 0;
220 other.m_iCapacity = 0;
221 }
222
223 Dictionary& operator=(Dictionary&& other) noexcept {
224 if (this != &other) {
225 FreeBuffer();
226 m_pBuckets = other.m_pBuckets;
227 m_pEntries = other.m_pEntries;
228 m_iCount = other.m_iCount;
229 m_iFreeList = other.m_iFreeList;
230 m_iFreeCount = other.m_iFreeCount;
231 m_iCapacity = other.m_iCapacity;
232 other.m_pBuckets = nullptr;
233 other.m_pEntries = nullptr;
234 other.m_iCount = 0;
235 other.m_iFreeList = -1;
236 other.m_iFreeCount = 0;
237 other.m_iCapacity = 0;
238 }
239 return *this;
240 }
241
242 int GetCount() const { return m_iCount - m_iFreeCount; }
243
244 void Add(const TKey& key, const TValue& value) {
245 Insert(key, value, true);
246 }
247
248 bool Remove(const TKey& key) {
249 if (m_pBuckets != nullptr) {
250 int hashCode = HashHelper<TKey>::GetHashCode(key) & 0x7FFFFFFF;
251 int bucket = hashCode % m_iCapacity;
252 int last = -1;
253 for (int i = m_pBuckets[bucket]; i >= 0; last = i, i = m_pEntries[i].next) {
254 if (m_pEntries[i].hashCode == hashCode && m_pEntries[i].key == key) {
255 if (last < 0) m_pBuckets[bucket] = m_pEntries[i].next;
256 else m_pEntries[last].next = m_pEntries[i].next;
257 m_pEntries[i].~Entry();
258 m_pEntries[i].hashCode = -1;
259 m_pEntries[i].next = m_iFreeList;
260 m_iFreeList = i;
261 m_iFreeCount++;
262 return true;
263 }
264 }
265 }
266 return false;
267 }
268
269 void Clear() {
270 if (m_iCount > 0) {
271 for (int i = 0; i < m_iCapacity; i++) m_pBuckets[i] = -1;
272 for (int i = 0; i < m_iCount; i++) {
273 if (m_pEntries[i].hashCode >= 0) m_pEntries[i].~Entry();
274 }
275 m_iFreeList = -1;
276 m_iCount = 0;
277 m_iFreeCount = 0;
278 }
279 }
280
281 bool ContainsKey(const TKey& key) const {
282 return FindEntry(key) >= 0;
283 }
284
285 bool TryGetValue(const TKey& key, TValue& value) const {
286 int i = FindEntry(key);
287 if (i >= 0) {
288 value = m_pEntries[i].value;
289 return true;
290 }
291 return false;
292 }
293
294 TValue& operator[](const TKey& key) {
295 int i = FindEntry(key);
296 if (i >= 0) return m_pEntries[i].value;
297 Insert(key, TValue(), false);
298 return m_pEntries[FindEntry(key)].value;
299 }
300
301 const TValue& operator[](const TKey& key) const {
302 int i = FindEntry(key);
303 if (i >= 0) return m_pEntries[i].value;
304 throw System::ArgumentException("Key not found.");
305 }
306
308 Array<TKey> arrKeys(GetCount());
309 int index = 0;
310 for (int i = 0; i < m_iCount; ++i) {
311 if (m_pEntries[i].hashCode >= 0) arrKeys[index++] = m_pEntries[i].key;
312 }
313 return arrKeys;
314 }
315
317 Array<TValue> arrValues(GetCount());
318 int index = 0;
319 for (int i = 0; i < m_iCount; ++i) {
320 if (m_pEntries[i].hashCode >= 0) arrValues[index++] = m_pEntries[i].value;
321 }
322 return arrValues;
323 }
324
325 class Iterator {
326 private:
327 const Dictionary* m_pDict;
328 int m_iIndex;
329 void AdvanceToValid() {
330 while (m_pDict && m_iIndex < m_pDict->m_iCount && m_pDict->m_pEntries[m_iIndex].hashCode < 0) {
331 m_iIndex++;
332 }
333 }
334 public:
335 Iterator(const Dictionary* dict, int index) : m_pDict(dict), m_iIndex(index) {
336 AdvanceToValid();
337 }
338 bool operator!=(const Iterator& other) const { return m_iIndex != other.m_iIndex; }
340 m_iIndex++;
341 AdvanceToValid();
342 return *this;
343 }
345 return KeyValuePair<TKey, TValue>(m_pDict->m_pEntries[m_iIndex].key, m_pDict->m_pEntries[m_iIndex].value);
346 }
347 };
348
349 Iterator begin() const { return Iterator(this, 0); }
350 Iterator end() const { return Iterator(this, m_iCount); }
351 };
352
353 }
354 }
355 }
356}
Defines the exception thrown when an invalid argument is provided to a method.
Provides methods for creating, manipulating, searching, and sorting arrays.
Defines common cross-platform macros, export decorators, and fundamental types.
Provides internal hashing algorithms and type specializations for computing 32-bit integer hash codes...
Defines the exception thrown when a method call is invalid for the object's current state.
Base object class for DotNetDupe mirroring .NET System.Object.
The exception that is thrown when one of the arguments provided to a method is not valid.
Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the ba...
Definition Array.h:29
bool TryGetValue(const TKey &key, TValue &value) const
Definition Dictionary.h:285
Dictionary & operator=(const Dictionary &other)
Definition Dictionary.h:199
const TValue & operator[](const TKey &key) const
Definition Dictionary.h:301
Dictionary & operator=(Dictionary &&other) noexcept
Definition Dictionary.h:223
void Add(const TKey &key, const TValue &value)
Definition Dictionary.h:244
Supports all classes in the DotNetDupe class hierarchy.
Definition Object.h:18
void * AllocateCollectionBuffer(size_t size)
Internal memory allocator for generic collections.
Definition Object.cpp:39
void FreeCollectionBuffer(void *p)
Frees collection buffer allocated with AllocateCollectionBuffer.
Definition Object.cpp:44
Provides prime modulus capacity calculation for hash-based collections.
Definition Dictionary.h:37
static const int s_primes[]
Array of prime capacities to distribute hash values evenly.
Definition Dictionary.h:39
static int GetPrime(int min)
Finds the smallest prime number greater than or equal to min.
Definition Dictionary.h:50
Defines a key/value pair that can be set or retrieved.
Definition Dictionary.h:22
KeyValuePair()
Initializes a new instance of the KeyValuePair structure with default values.
Definition Dictionary.h:27
TKey Key
Gets or sets the key in the key/value pair.
Definition Dictionary.h:23
TValue Value
Gets or sets the value in the key/value pair.
Definition Dictionary.h:24
KeyValuePair(TKey k, TValue v)
Initializes a new instance of the KeyValuePair structure with the specified key and value.
Definition Dictionary.h:32
static int GetHashCode(const T &value)
Computes a 32-bit hash code for the specified value.
Definition HashHelper.h:25