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 <ae@cy.md>
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* opBinaryRight(string op)(in K k)
61 		if (op == "in")
62 		{
63 			return get(k); // Issue 11842
64 		}
65 
66 		V get(in K k, V def)
67 		{
68 			auto pv = get(k);
69 			return pv ? *pv : def;
70 		}
71 
72 		/// Returns a pointer to the value storage space for a new value.
73 		/// Assumes the key does not yet exist in the table.
74 		V* add(in K k)
75 		{
76 			auto h = hashFunc(k) % SIZE;
77 			auto newItem = ALLOCATOR.allocate!Item();
78 			newItem.k = k;
79 			newItem.next = data.items[h];
80 			data.items[h] = newItem;
81 			return &newItem.v;
82 		}
83 
84 		/// Returns a pointer to the value storage space for a new
85 		/// or existing value.
86 		V* getOrAdd(in K k)
87 		{
88 			auto h = hashFunc(k) % SIZE;
89 			auto item = data.items[h];
90 			while (item)
91 			{
92 				if (item.k == k)
93 					return &item.v;
94 				item = item.next;
95 			}
96 
97 			auto newItem = ALLOCATOR.allocate!Item();
98 			newItem.k = k;
99 			newItem.next = data.items[h];
100 			data.items[h] = newItem;
101 			return &newItem.v;
102 		}
103 
104 		void set(in K k, ref V v) { *getOrAdd(k) = v; }
105 
106 		int opApply(int delegate(ref K, ref V) dg)
107 		{
108 			int result = 0;
109 
110 			outerLoop:
111 			for (uint h=0; h<SIZE; h++)
112 			{
113 				auto item = data.items[h];
114 				while (item)
115 				{
116 					result = dg(item.k, item.v);
117 					if (result)
118 						break outerLoop;
119 					item = item.next;
120 				}
121 			}
122 			return result;
123 		}
124 
125 		ref V opIndex(in K k)
126 		{
127 			auto pv = get(k);
128 			enforce(pv, "Key not in HashTable");
129 			return *pv;
130 		}
131 
132 		void opIndexAssign(ref V v, in K k) { set(k, v); }
133 
134 		size_t getLength()
135 		{
136 			size_t count = 0;
137 			for (uint h=0; h<SIZE; h++)
138 			{
139 				auto item = data.items[h];
140 				while (item)
141 				{
142 					count++;
143 					item = item.next;
144 				}
145 			}
146 			return count;
147 		}
148 
149 		void clear()
150 		{
151 			data.items[] = null;
152 		}
153 
154 		void freeAll()
155 		{
156 			static if (is(typeof(ALLOCATOR.freeAll())))
157 				ALLOCATOR.freeAll();
158 		}
159 	}
160 }
161 
162 unittest
163 {
164 	import ae.utils.alloc;
165 
166 	static struct Test
167 	{
168 		WrapParts!(RegionAllocator!()) allocator;
169 		alias HashTable!(int, string, 16, allocator) HT;
170 		HT.Data htData;
171 		alias ht = HT.Impl!htData;
172 
173 		mixin(mixAliasForward!(ht, q{ht}));
174 	}
175 	Test ht;
176 
177 	assert(5 !in ht);
178 	auto s = "five";
179 	ht[5] = s;
180 	assert(5 in ht);
181 	assert(ht[5] == "five");
182 }