1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity low)
4 module dil.AArray;
5 
6 import common;
7 
8 import core.bitop : bsr;
9 
10 alias Key = size_t;
11 alias Value = void*;
12 
13 /// An item in an associative array. Can be chained together with other items.
14 struct AANode
15 {
16   Key key; /// A hash value.
17   Value value; /// The value associated with the key.
18   AANode* next; /// Links to the next node.
19 }
20 
21 /// An associative array implementation which grows by powers of two.
22 /// Relies on the GC for memory. Does not handle hash collisions.
23 /// Note: This code is a reference for understanding how hash maps work.
24 ///   It's not meant to be used in DIL. A fast version needs to be implemented
25 ///   which uses dil.Array for custom allocation.
26 struct AArray
27 {
28   AANode*[] buckets = [null]; /// The number of buckets is a power of 2.
29   size_t count; /// Number of nodes.
30 
31   /// Returns the number of nodes.
32   size_t len()
33   {
34     return count;
35   }
36 
37   /// Returns the number of buckets.
38   size_t blen()
39   {
40     return buckets.length;
41   }
42 
43   /// Returns the index of a key in the buckets array.
44   size_t toindex(Key key)
45   { // This is basically a modulo operation: key % blen()
46     // But since the length will always be a power of 2,
47     // the and-operator can be used as a faster method.
48     return key & (blen() - 1);
49   }
50 
51   /// Returns the value of a key, or null if it doesn't exist.
52   Value get(Key key)
53   {
54     if (auto n = find(key))
55       return n.value;
56     return null;
57   }
58 
59   /// Sets the value of a key.
60   void set(Key key, Value value)
61   {
62     getadd(key).value = value;
63   }
64 
65   /// Adds a pair to the array, assuming it doesn't exist already.
66   void add(Key key, Value value)
67   {
68     assert(find(key) is null);
69     auto i = toindex(key);
70     auto pbucket = &buckets[i];
71     *pbucket = new AANode(key, value, *pbucket);
72     count++;
73     if (count > blen() * 2)
74       rehash();
75   }
76 
77   /// Finds a node by key or adds a new one if inexistent.
78   /// Useful when the AA is on the lhs, e.g.: aa["b"] = 1;
79   AANode* getadd(Key key)
80   {
81     auto i = toindex(key);
82     auto pbucket = &buckets[i];
83     for (auto n = *pbucket; n; n = n.next)
84       if (n.key == key)
85         return n; // Found the node.
86     *pbucket = new AANode(key, null, *pbucket); // Create a new node.
87     count++;
88     if (count > blen() * 2)
89       rehash();
90     return *pbucket;
91   }
92 
93   /// Removes a key value pair from the array.
94   /// Returns: true if the item was removed, false if it didn't exist.
95   bool remove(Key key)
96   {
97     auto i = toindex(key);
98     AANode** pn = &buckets[i]; /// Reference to the previous node.
99     for (auto n = *pn; n; n = n.next)
100     {
101       if (n.key == key)
102       {
103         *pn = n.next;
104         count--;
105         return true;
106       }
107       pn = &n.next;
108     }
109     return false;
110   }
111 
112   /// Finds the node matching the key.
113   AANode* find(Key key)
114   {
115     auto i = toindex(key);
116     for (auto n = buckets[i]; n; n = n.next)
117       if (n.key == key)
118         return n;
119     return null;
120   }
121 
122   /// Allocates a new bucket list and relocates the nodes from the old one.
123   void rehash()
124   {
125     auto newlen = this.count;
126     if (!newlen)
127       return;
128     // Check if not a power of 2.
129     if (newlen & (newlen - 1))
130     { // Round up to the next power of 2.
131       newlen = 2 << bsr(newlen);
132       if (newlen == 0) // Did it overflow?
133         newlen = size_t.max / 2 + 1; // Set the highest bit.
134     }
135     assert(newlen && !(newlen & (newlen - 1)), "zero or not power of 2");
136     // Allocate a new list of buckets.
137     AANode*[] newb = new AANode*[newlen];
138     newlen--; // Subtract now to avoid doing it in the loop.
139     // Move the nodes to the new array.
140     foreach (n; buckets)
141       while (n)
142       {
143         auto next_node = n.next;
144         size_t i = n.key & newlen;
145         n.next = newb[i];
146         newb[i] = n; // n becomes the new head at index i.
147         n = next_node; // Continue with the next node in the chain.
148       }
149     buckets = newb;
150   }
151 
152   /// Formats a number as a string with hexadecimal characters.
153   static char[] toHex(size_t x)
154   {
155     immutable H = "0123456789abcdef"; // Hex numerals.
156     auto s = new char[size_t.sizeof * 2 + 2];
157     s[0] = '0';
158     s[1] = 'x';
159     auto p = s.ptr + s.length;
160     while (*--p != 'x')
161     {
162       *p = H[x & 0xF];
163       p--;
164       x >>= 4;
165       *p = H[x & 0xF];
166       x >>= 4;
167     }
168     return s;
169   }
170 
171   /// Prints as hex by default.
172   static cstring keyPrinter(Key k)
173   {
174     return toHex(k);
175   }
176 
177   /// Prints as hex by default.
178   static cstring valuePrinter(Value v)
179   {
180     return toHex(cast(size_t)v);
181   }
182 
183   /// Prints the contents of this array.
184   /// Supply own functions for customization.
185   char[] print(cstring function(Key) printKey = &keyPrinter,
186                cstring function(Value) printValue = &valuePrinter)
187   {
188     char[] s = "[".dup;
189     foreach (i, n; buckets)
190     {
191       if (i)
192         s ~= "], ";
193       s ~= "[";
194       while (n)
195       {
196         s ~= "(";
197         s ~= printKey(n.key);
198         s ~= ", ";
199         s ~= printValue(n.value);
200         s ~= ")";
201         n = n.next;
202         if (n)
203           s ~= ", ";
204       }
205     }
206     s ~= "]";
207     return s;
208   }
209 }