xref: /haiku/headers/private/kernel/util/OpenHashTable.h (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
1 /*
2  * Copyright 2007, Hugo Santos. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 #ifndef _KERNEL_UTIL_OPEN_HASH_TABLE_H
6 #define _KERNEL_UTIL_OPEN_HASH_TABLE_H
7 
8 
9 #include <OS.h>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #ifdef _KERNEL_MODE
14 #	include <KernelExport.h>
15 #	include <util/kernel_cpp.h>
16 #endif
17 
18 
19 /*!
20 	The Definition template must have four methods: `HashKey', `Hash',
21 	`Compare' and `GetLink;. It must also define several types as shown in the
22 	following example:
23 
24 	struct Foo {
25 		int bar;
26 
27 		Foo* fNext;
28 	};
29 
30 	struct HashTableDefinition {
31 		typedef int		KeyType;
32 		typedef	Foo		ValueType;
33 
34 		HashTableDefinition(const HashTableDefinition&) {}
35 
36 		size_t HashKey(int key) const { return key >> 1; }
37 		size_t Hash(Foo* value) const { return HashKey(value->bar); }
38 		bool Compare(int key, Foo* value) const { return value->bar == key; }
39 		Foo*& GetLink(Foo* value) const { return value->fNext; }
40 	};
41 */
42 
43 
44 template<typename Definition, bool AutoExpand = true,
45 	bool CheckDuplicates = false>
46 class BOpenHashTable {
47 public:
48 	typedef BOpenHashTable<Definition, AutoExpand, CheckDuplicates> HashTable;
49 	typedef typename Definition::KeyType	KeyType;
50 	typedef typename Definition::ValueType	ValueType;
51 
52 	static const size_t kMinimumSize = 8;
53 
54 	// we use malloc() / free() for allocation. If in the future this
55 	// is revealed to be insufficient we can switch to a template based
56 	// allocator. All allocations are of power of 2 lengths.
57 
58 	// regrowth factor: 200 / 256 = 78.125%
59 	//                   50 / 256 = 19.53125%
60 
61 	BOpenHashTable()
62 		:
63 		fTableSize(0),
64 		fItemCount(0),
65 		fTable(NULL)
66 	{
67 	}
68 
69 	BOpenHashTable(const Definition& definition)
70 		:
71 		fDefinition(definition),
72 		fTableSize(0),
73 		fItemCount(0),
74 		fTable(NULL)
75 	{
76 	}
77 
78 	~BOpenHashTable()
79 	{
80 		free(fTable);
81 	}
82 
83 	status_t Init(size_t initialSize = kMinimumSize)
84 	{
85 		if (initialSize > 0 && !_Resize(initialSize))
86 			return B_NO_MEMORY;
87 		return B_OK;
88 	}
89 
90 	size_t TableSize() const
91 	{
92 		return fTableSize;
93 	}
94 
95 	size_t CountElements() const
96 	{
97 		return fItemCount;
98 	}
99 
100 	ValueType* Lookup(const KeyType& key) const
101 	{
102 		if (fTableSize == 0)
103 			return NULL;
104 
105 		size_t index = fDefinition.HashKey(key) & (fTableSize - 1);
106 		ValueType* slot = fTable[index];
107 
108 		while (slot) {
109 			if (fDefinition.Compare(key, slot))
110 				break;
111 			slot = _Link(slot);
112 		}
113 
114 		return slot;
115 	}
116 
117 	status_t Insert(ValueType* value)
118 	{
119 		if (fTableSize == 0) {
120 			if (!_Resize(kMinimumSize))
121 				return B_NO_MEMORY;
122 		} else if (AutoExpand && fItemCount >= (fTableSize * 200 / 256))
123 			_Resize(fTableSize * 2);
124 
125 		InsertUnchecked(value);
126 		return B_OK;
127 	}
128 
129 	void InsertUnchecked(ValueType* value)
130 	{
131 		if (CheckDuplicates && _ExhaustiveSearch(value)) {
132 #ifdef _KERNEL_MODE
133 			panic("Hash Table: value already in table.");
134 #else
135 			debugger("Hash Table: value already in table.");
136 #endif
137 		}
138 
139 		_Insert(fTable, fTableSize, value);
140 		fItemCount++;
141 	}
142 
143 	// TODO: a ValueType* Remove(const KeyType& key) method is missing
144 
145 	bool Remove(ValueType* value)
146 	{
147 		if (!RemoveUnchecked(value))
148 			return false;
149 
150 		if (AutoExpand && fTableSize > kMinimumSize
151 			&& fItemCount < (fTableSize * 50 / 256))
152 			_Resize(fTableSize / 2);
153 
154 		return true;
155 	}
156 
157 	bool RemoveUnchecked(ValueType* value)
158 	{
159 		size_t index = fDefinition.Hash(value) & (fTableSize - 1);
160 		ValueType* previous = NULL;
161 		ValueType* slot = fTable[index];
162 
163 		while (slot) {
164 			ValueType* next = _Link(slot);
165 
166 			if (value == slot) {
167 				if (previous)
168 					_Link(previous) = next;
169 				else
170 					fTable[index] = next;
171 				break;
172 			}
173 
174 			previous = slot;
175 			slot = next;
176 		}
177 
178 		if (slot == NULL)
179 			return false;
180 
181 		if (CheckDuplicates && _ExhaustiveSearch(value)) {
182 #ifdef _KERNEL_MODE
183 			panic("Hash Table: duplicate detected.");
184 #else
185 			debugger("Hash Table: duplicate detected.");
186 #endif
187 		}
188 
189 		fItemCount--;
190 		return true;
191 	}
192 
193 	/*!	Removes all elements from the hash table. No resizing happens. The
194 		elements are not deleted. If \a returnElements is \c true, the method
195 		returns all elements chained via their hash table link.
196 	*/
197 	ValueType* Clear(bool returnElements = false)
198 	{
199 		if (this->fItemCount == 0)
200 			return NULL;
201 
202 		ValueType* result = NULL;
203 
204 		if (returnElements) {
205 			ValueType** nextPointer = &result;
206 
207 			// iterate through all buckets
208 			for (size_t i = 0; i < fTableSize; i++) {
209 				ValueType* element = fTable[i];
210 				if (element != NULL) {
211 					// add the bucket to the list
212 					*nextPointer = element;
213 
214 					// update nextPointer to point to the fNext of the last
215 					// element in the bucket
216 					while (element != NULL) {
217 						nextPointer = &_Link(element);
218 						element = *nextPointer;
219 					}
220 				}
221 			}
222 		}
223 
224 		memset(this->fTable, 0, sizeof(ValueType*) * this->fTableSize);
225 
226 		return result;
227 	}
228 
229 	/*!	If the table needs resizing, the number of bytes for the required
230 		allocation is returned. If no resizing is needed, 0 is returned.
231 	*/
232 	size_t ResizeNeeded() const
233 	{
234 		size_t size = fTableSize;
235 		if (size == 0 || fItemCount >= (size * 200 / 256)) {
236 			// grow table
237 			if (size == 0)
238 				size = kMinimumSize;
239 			while (fItemCount >= size * 200 / 256)
240 				size <<= 1;
241 		} else if (size > kMinimumSize && fItemCount < size * 50 / 256) {
242 			// shrink table
243 			while (fItemCount < size * 50 / 256)
244 				size >>= 1;
245 			if (size < kMinimumSize)
246 				size = kMinimumSize;
247 		}
248 
249 		if (size == fTableSize)
250 			return 0;
251 
252 		return size * sizeof(ValueType*);
253 	}
254 
255 	/*!	Resizes the table using the given allocation. The allocation must not
256 		be \c NULL. It must be of size \a size, which must a value returned
257 		earlier by ResizeNeeded(). If the size requirements have changed in the
258 		meantime, the method free()s the given allocation and returns \c false.
259 		Otherwise \c true is returned.
260 	*/
261 	bool Resize(void* allocation, size_t size)
262 	{
263 		if (size != ResizeNeeded()) {
264 			free(allocation);
265 			return false;
266 		}
267 
268 		_Resize((ValueType**)allocation, size / sizeof(ValueType*));
269 		return true;
270 	}
271 
272 	class Iterator {
273 	public:
274 		Iterator(const HashTable* table)
275 			: fTable(table)
276 		{
277 			Rewind();
278 		}
279 
280 		Iterator(const HashTable* table, size_t index, ValueType* value)
281 			: fTable(table), fIndex(index), fNext(value) {}
282 
283 		bool HasNext() const { return fNext != NULL; }
284 
285 		ValueType* Next()
286 		{
287 			ValueType* current = fNext;
288 			_GetNext();
289 			return current;
290 		}
291 
292 		void Rewind()
293 		{
294 			// get the first one
295 			fIndex = 0;
296 			fNext = NULL;
297 			_GetNext();
298 		}
299 
300 	protected:
301 		Iterator() {}
302 
303 		void _GetNext()
304 		{
305 			if (fNext)
306 				fNext = fTable->_Link(fNext);
307 
308 			while (fNext == NULL && fIndex < fTable->fTableSize)
309 				fNext = fTable->fTable[fIndex++];
310 		}
311 
312 		const HashTable* fTable;
313 		size_t fIndex;
314 		ValueType* fNext;
315 	};
316 
317 	Iterator GetIterator() const
318 	{
319 		return Iterator(this);
320 	}
321 
322 	Iterator GetIterator(const KeyType& key) const
323 	{
324 		if (fTableSize == 0)
325 			return Iterator(this, fTableSize, NULL);
326 
327 		size_t index = fDefinition.HashKey(key) & (fTableSize - 1);
328 		ValueType* slot = fTable[index];
329 
330 		while (slot) {
331 			if (fDefinition.Compare(key, slot))
332 				break;
333 			slot = _Link(slot);
334 		}
335 
336 		if (slot == NULL)
337 			return Iterator(this, fTableSize, NULL);
338 
339 		return Iterator(this, index + 1, slot);
340 	}
341 
342 protected:
343 	// for g++ 2.95
344 	friend class Iterator;
345 
346 	void _Insert(ValueType** table, size_t tableSize, ValueType* value)
347 	{
348 		size_t index = fDefinition.Hash(value) & (tableSize - 1);
349 
350 		_Link(value) = table[index];
351 		table[index] = value;
352 	}
353 
354 	bool _Resize(size_t newSize)
355 	{
356 		ValueType** newTable
357 			= (ValueType**)malloc(sizeof(ValueType*) * newSize);
358 		if (newTable == NULL)
359 			return false;
360 
361 		_Resize(newTable, newSize);
362 		return true;
363 	}
364 
365 	void _Resize(ValueType** newTable, size_t newSize)
366 	{
367 		for (size_t i = 0; i < newSize; i++)
368 			newTable[i] = NULL;
369 
370 		if (fTable) {
371 			for (size_t i = 0; i < fTableSize; i++) {
372 				ValueType* bucket = fTable[i];
373 				while (bucket) {
374 					ValueType* next = _Link(bucket);
375 					_Insert(newTable, newSize, bucket);
376 					bucket = next;
377 				}
378 			}
379 
380 			free(fTable);
381 		}
382 
383 		fTableSize = newSize;
384 		fTable = newTable;
385 	}
386 
387 	ValueType*& _Link(ValueType* bucket) const
388 	{
389 		return fDefinition.GetLink(bucket);
390 	}
391 
392 	bool _ExhaustiveSearch(ValueType* value) const
393 	{
394 		for (size_t i = 0; i < fTableSize; i++) {
395 			ValueType* bucket = fTable[i];
396 			while (bucket) {
397 				if (bucket == value)
398 					return true;
399 				bucket = _Link(bucket);
400 			}
401 		}
402 
403 		return false;
404 	}
405 
406 	Definition		fDefinition;
407 	size_t			fTableSize;
408 	size_t			fItemCount;
409 	ValueType**		fTable;
410 };
411 
412 #endif	// _KERNEL_UTIL_OPEN_HASH_TABLE_H
413