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