1 /**
2  * ae.utils.container.hashtable
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.container.hashtable;
15 
16 struct HashTableItem(K, V)
17 {
18 	K k;
19 	HashTableItem* next;
20 	V v;
21 }
22 
23 /// A hash table with a static size.
24 struct HashTable(K, V, uint SIZE, alias ALLOCATOR, alias HASHFUNC="k")
25 {
26 	alias K KEY;
27 	alias V VALUE;
28 
29 	import std.functional;
30 	import std.exception;
31 
32 	alias unaryFun!(HASHFUNC, "k") hashFunc;
33 
34 	// hashFunc returns a hash, get its type
35 	alias typeof(hashFunc(K.init)) H;
36 	static assert(is(H : ulong), "Numeric hash type expected");
37 
38 	alias HashTableItem!(K, V) Item;
39 
40 	struct Data
41 	{
42 		Item*[SIZE] items;
43 	}
44 
45 	static template Impl(alias data)
46 	{
47 		V* get(in K k)
48 		{
49 			auto h = hashFunc(k) % SIZE;
50 			auto item = data.items[h];
51 			while (item)
52 			{
53 				if (item.k == k)
54 					return &item.v;
55 				item = item.next;
56 			}
57 			return null;
58 		}
59 
60 		V* opIn_r(in K k) { return get(k); } // Issue 11842
61 
62 		V get(in K k, V def)
63 		{
64 			auto pv = get(k);
65 			return pv ? *pv : def;
66 		}
67 
68 		/// Returns a pointer to the value storage space for a new value.
69 		/// Assumes the key does not yet exist in the table.
70 		V* add(in K k)
71 		{
72 			auto h = hashFunc(k) % SIZE;
73 			auto newItem = ALLOCATOR.allocate!Item();
74 			newItem.k = k;
75 			newItem.next = data.items[h];
76 			data.items[h] = newItem;
77 			return &newItem.v;
78 		}
79 
80 		/// Returns a pointer to the value storage space for a new
81 		/// or existing value.
82 		V* getOrAdd(in K k)
83 		{
84 			auto h = hashFunc(k) % SIZE;
85 			auto item = data.items[h];
86 			while (item)
87 			{
88 				if (item.k == k)
89 					return &item.v;
90 				item = item.next;
91 			}
92 
93 			auto newItem = ALLOCATOR.allocate!Item();
94 			newItem.k = k;
95 			newItem.next = data.items[h];
96 			data.items[h] = newItem;
97 			return &newItem.v;
98 		}
99 
100 		void set(in K k, ref V v) { *getOrAdd(k) = v; }
101 
102 		int opApply(int delegate(ref K, ref V) dg)
103 		{
104 			int result = 0;
105 
106 			outerLoop:
107 			for (uint h=0; h<SIZE; h++)
108 			{
109 				auto item = data.items[h];
110 				while (item)
111 				{
112 					result = dg(item.k, item.v);
113 					if (result)
114 						break outerLoop;
115 					item = item.next;
116 				}
117 			}
118 			return result;
119 		}
120 
121 		ref V opIndex(in K k)
122 		{
123 			auto pv = get(k);
124 			enforce(pv, "Key not in HashTable");
125 			return *pv;
126 		}
127 
128 		void opIndexAssign(ref V v, in K k) { set(k, v); }
129 
130 		size_t getLength()
131 		{
132 			size_t count = 0;
133 			for (uint h=0; h<SIZE; h++)
134 			{
135 				auto item = data.items[h];
136 				while (item)
137 				{
138 					count++;
139 					item = item.next;
140 				}
141 			}
142 			return count;
143 		}
144 
145 		void clear()
146 		{
147 			data.items[] = null;
148 		}
149 
150 		void freeAll()
151 		{
152 			static if (is(typeof(ALLOCATOR.freeAll())))
153 				ALLOCATOR.freeAll();
154 		}
155 	}
156 }
157 
158 unittest
159 {
160 	import ae.utils.alloc;
161 
162 	static struct Test
163 	{
164 		WrapParts!(RegionAllocator!()) allocator;
165 		alias HashTable!(int, string, 16, allocator) HT;
166 		HT.Data htData;
167 		alias ht = HT.Impl!htData;
168 
169 		mixin(mixAliasForward!(ht, q{ht}));
170 	}
171 	Test ht;
172 
173 	assert(5 !in ht);
174 	auto s = "five";
175 	ht[5] = s;
176 	assert(5 in ht);
177 	assert(ht[5] == "five");
178 }