OLD | NEW |
---|---|
(Empty) | |
1 part of dart.collection; | |
2 | |
3 class _DeadEntry { | |
4 const _DeadEntry(); | |
5 } | |
6 | |
7 class _NullKey { | |
8 const _NullKey(); | |
9 int get hashCode => null.hashCode; | |
10 bool operator ==(Object other) => other == null || identical(this, other); | |
floitsch
2013/02/08 16:48:35
that can't happen. other will never be 'null'.
Lasse Reichstein Nielsen
2013/02/11 14:07:37
Actually, it will never be _NULL.
The incoming val
floitsch
2013/02/11 14:09:43
But the spec for x == y already guarantees that if
Lasse Reichstein Nielsen
2013/02/12 10:34:47
Doh. So it's not working. More tests needed.
| |
11 } | |
12 | |
13 const _TOMBSTONE = const _DeadEntry(); | |
14 const _NULL = const _NullKey(); | |
15 | |
16 class _HashTable<K> { | |
17 /** | |
18 * Table of entries with [_entrySize] slots per entry. | |
19 * | |
20 * Capacity in entries must be factor of two. | |
21 */ | |
22 List _table; | |
23 /** Current capacity. Always equal to [:_table.length ~/ _entrySize:]. */ | |
24 int _capacity; | |
25 /** Count of occupied entries, including deleted ones. */ | |
26 int _entryCount = 0; | |
27 /** Count of deleted entries. */ | |
28 int _deletedCount = 0; | |
29 /** Counter incremented when table is modified. */ | |
30 int _modificationCount = 0; | |
31 | |
32 _HashTable(int initialCapacity) : _capacity = initialCapacity { | |
33 _table = _createTable(initialCapacity); | |
34 } | |
35 | |
36 /** Reads key from table. Converts _NULL marker to null. */ | |
37 Object _key(offset) { | |
38 assert(!_isFree(_table[offset])); | |
39 Object key = _table[offset]; | |
40 if (!identical(key, _NULL)) return key; | |
41 return null; | |
42 } | |
43 | |
44 /** Writes key to table. Converts _NULL marker to null. */ | |
floitsch
2013/02/08 16:48:35
Converts null to _NULL marker.
Lasse Reichstein Nielsen
2013/02/11 14:07:37
Done.
| |
45 void _setKey(int offset, Object key) { | |
46 if (key == null) key = _NULL; | |
47 _table[offset] = key; | |
48 } | |
49 | |
50 int get _elementCount => _entryCount - _deletedCount; | |
51 | |
52 /** Size of each entry. */ | |
53 int get _entrySize => 1; | |
54 | |
55 void _checkModification(int expectedModificationCount) { | |
56 if (_modificationCount != expectedModificationCount) { | |
57 throw new ConcurrentModificationError(this); | |
58 } | |
59 } | |
60 | |
61 void _recordModification() { | |
62 // Value cycles after 2^30 modifications. If you keep hold of an | |
63 // iterator for that long, you might miss a modification detection, | |
64 // and iteration can go sour. Don't do that. | |
65 _modificationCount = (_modificationCount + 1) & (0x3FFFFFFF); | |
66 } | |
67 | |
68 /** | |
69 * Create an empty table. | |
70 */ | |
71 List _createTable(int capacity) { | |
72 List table = new List.fixedLength(capacity * _entrySize); | |
73 return table; | |
74 } | |
75 | |
76 /** First table probe. */ | |
77 int _firstProbe(int hashCode, int capacity) { | |
78 return hashCode & (capacity - 1); | |
79 } | |
80 | |
81 /** Following table probes. */ | |
82 int _nextProbe(int previousIndex, int probeCount, int capacity) { | |
83 return (previousIndex + probeCount) & (capacity - 1); | |
84 } | |
85 | |
86 /** Whether an object is a free-marker (either tombstone or free). */ | |
87 bool _isFree(Object marker) => | |
88 marker == null || identical(marker, _TOMBSTONE); | |
89 | |
90 /** | |
91 * Look up the offset for an object in the table. | |
92 * | |
93 * Finds the offset of the object in the table, if it is there, | |
94 * or the first free offset for its hashCode. | |
95 */ | |
96 int _probeForAdd(int hashCode, Object object) { | |
97 int entrySize = _entrySize; | |
98 int index = _firstProbe(hashCode, _capacity); | |
99 int firstTombstone = -1; | |
100 int probeCount = 0; | |
101 while (true) { | |
102 int offset = index * entrySize; | |
103 Object entry = _table[offset]; | |
104 if (identical(entry, _TOMBSTONE)) { | |
105 if (firstTombstone < 0) firstTombstone = offset; | |
106 } else if (entry == null) { | |
107 if (firstTombstone < 0) return offset; | |
108 return firstTombstone; | |
109 } else if (_equals(entry, object)) { | |
110 // TODO(lrn): Test if caching the last found key | |
111 // is better than (almost) always reading it again. | |
112 return offset; | |
113 } | |
114 // The _nextProbe must be designed so that it hits | |
115 // every index eventually. | |
116 index = _nextProbe(index, ++probeCount, _capacity); | |
117 } | |
118 } | |
119 | |
120 int _probeForLookup(int hashCode, Object object) { | |
121 int entrySize = _entrySize; | |
122 int index = _firstProbe(hashCode, _capacity); | |
123 int probeCount = 0; | |
124 while (true) { | |
125 int offset = index * entrySize; | |
126 Object entry = _table[offset]; | |
127 if (entry == null) { | |
128 return -1; | |
129 } else if (_equals(entry, object)) { | |
130 // If entry it's _TOMBSTONE, it matches nothing. | |
131 // Consider special casing it to make equals calls monomorphic. | |
132 return offset; | |
133 } | |
134 // The _nextProbe must be designed so that it hits | |
135 // every index eventually. | |
136 index = _nextProbe(index, ++probeCount, _capacity); | |
137 } | |
138 } | |
139 | |
140 // Override the following two to change equality/hashCode computations | |
141 | |
142 /** | |
143 * Compare two object for equality. | |
144 * | |
145 * The first object is the one already in the table, | |
146 * and the second is the one being searched for. | |
147 */ | |
148 bool _equals(Object element, Object other) { | |
149 return element == other; | |
150 } | |
151 | |
152 /** | |
153 * Compute hash-code for an object. | |
154 */ | |
155 int _hashCodeOf(Object object) => object.hashCode; | |
156 | |
157 /** | |
158 * Ensure that the table has room to add [moreElements] entries. | |
159 * | |
160 * Call this before adding one or more elements. | |
161 */ | |
162 int _ensureCapacity(int moreElements) { | |
163 // Compute everything in multiples of entrySize to avoid division. | |
164 int entrySize = _entrySize; | |
165 int capacity = _capacity; | |
166 // Assume worst-case where no deleted elements are reused. | |
167 int newEntryCount = _entryCount + moreElements; | |
168 int newFreeCount = capacity - newEntryCount; | |
169 if (newFreeCount * 4 < capacity || | |
170 newFreeCount < _deletedCount) { | |
171 // Less than 25% free or fewer free entries than deleted entries. | |
172 _grow(_entryCount - _deletedCount + moreElements); | |
173 } | |
174 } | |
175 | |
176 void _grow(int contentCount) { | |
177 int capacity = _capacity; | |
178 // Don't grow to less than twice the needed capacity. | |
179 int minCapacity = contentCount * 2; | |
180 while (capacity < minCapacity) { | |
181 capacity *= 2; | |
182 } | |
183 // Reset to another table and add all existing elements. | |
184 List oldTable = _table; | |
185 _table = _createTable(capacity); | |
186 _capacity = capacity; | |
187 _entryCount = 0; | |
188 _deletedCount = 0; | |
189 _addAllEntries(oldTable); | |
190 _recordModification(); | |
191 } | |
192 | |
193 /** | |
194 * Copies all non-free entries from the old table to the new empty table. | |
195 */ | |
196 void _addAllEntries(List oldTable) { | |
197 for (int i = 0; i < oldTable.length; i += _entrySize) { | |
198 Object object = oldTable[i]; | |
199 if (!_isFree(object)) { | |
200 int toOffset = _put(object); | |
201 _copyEntry(oldTable, i, toOffset); | |
202 } | |
203 } | |
204 } | |
205 | |
206 /** | |
207 * Copies everything but the key element from one entry to another. | |
208 * | |
209 * Called while growing the base array. | |
210 * | |
211 * Override this if any non-key fields need copying. | |
212 */ | |
213 void _copyEntry(List fromTable, int fromOffset, int toOffset) {} | |
214 | |
215 // The following three methods are for simple get/set/remove operations. | |
216 // They only affect the key of an entry. The remaining fields must be | |
217 // filled by the caller. | |
218 | |
219 /** | |
220 * Returns the offset of a key in [_table], or negative if it's not there. | |
221 */ | |
222 int _get(K key) { | |
223 return _probeForLookup(_hashCodeOf(key), key); | |
224 } | |
225 | |
226 /** | |
227 * Puts the key into the table and returns its offset into [_table]. | |
228 * | |
229 * If [_entrySize] is greater than 1, the caller should fill the | |
230 * remaining fields. | |
231 * | |
232 * Remember to call [_ensureCapacity] before using this method. | |
233 * You can call it once with a larger number before doing a sequence | |
234 * of put operations. | |
235 */ | |
236 int _put(K key) { | |
237 int offset = _probeForAdd(_hashCodeOf(key), key); | |
238 Object oldEntry = _table[offset]; | |
239 if (oldEntry == null) { | |
240 _entryCount++; | |
241 } else if (identical(oldEntry, _TOMBSTONE)) { | |
242 _deletedCount--; | |
243 } else { | |
244 return offset; | |
245 } | |
246 _setKey(offset, key); | |
247 _recordModification(); | |
248 return offset; | |
249 } | |
250 | |
251 /** | |
252 * Removes a key from the table and returns its offset into [_table]. | |
253 * | |
254 * Returns null if the key was not in the table. | |
255 * If [_entrySize] is greater than 1, the caller should clean up the | |
256 * remaining fields. | |
257 */ | |
258 int _remove(K key) { | |
259 int offset = _probeForLookup(_hashCodeOf(key), key); | |
260 if (offset >= 0) { | |
261 _deleteEntry(offset); | |
262 } | |
263 return offset; | |
264 } | |
265 | |
266 /** Clears the table completely, leaving it empty. */ | |
267 void _clear() { | |
268 if (_elementCount == 0) return; | |
269 for (int i = 0; i < _table.length; i++) { | |
270 _table[i] = null; | |
271 } | |
272 _entryCount = _deletedCount = 0; | |
273 _recordModification(); | |
274 } | |
275 | |
276 /** Clears an entry in the table. */ | |
277 void _deleteEntry(int offset) { | |
278 assert(!_isFree(_table[offset])); | |
279 _setKey(offset, _TOMBSTONE); | |
280 _deletedCount++; | |
281 _recordModification(); | |
282 } | |
283 } | |
284 | |
285 class _HashTableKeyIterable<K> extends Iterable<K> { | |
286 final _HashTable<K> _hashTable; | |
287 _HashTableKeyIterable(this._hashTable); | |
288 Iterator<K> get iterator => new _HashTableKeyIterator<K>(_hashTable); | |
289 | |
290 int get length => _hashTable._elementCount; | |
291 | |
292 bool contains(Object value) => _hashTable._get(value) >= 0; | |
293 | |
294 bool get isEmpty => _hashTable._elementCount == 0; | |
295 | |
296 K get single { | |
297 if (_hashTable._elementCount > 1) { | |
298 throw new StateError("More than one element"); | |
299 } | |
300 return first; | |
301 } | |
302 } | |
303 | |
304 class _HashTableKeyIterator<K> implements Iterator<K> { | |
305 final _HashTable<K> _hashTable; | |
306 final int _modificationCount; | |
307 /** Location right after last found element. */ | |
308 int _offset = 0; | |
309 K _current = null; | |
310 | |
311 _HashTableKeyIterator(_HashTable<K> hashTable) | |
312 : _hashTable = hashTable, | |
313 _modificationCount = hashTable._modificationCount; | |
314 | |
315 bool moveNext() { | |
316 _hashTable._checkModification(_modificationCount); | |
317 | |
318 List table = _hashTable._table; | |
319 int entrySize = _hashTable._entrySize; | |
320 | |
321 while (_offset < table.length) { | |
322 Object entry = table[_offset]; | |
323 _offset += entrySize; | |
324 if (!_hashTable._isFree(entry)) { | |
325 if (identical(entry, _NULL)) { | |
326 _current = null; | |
327 } else { | |
328 _current = entry; | |
329 } | |
330 return true; | |
331 } | |
332 } | |
333 _current = null; | |
334 return false; | |
335 } | |
336 | |
337 K get current => _current; | |
338 } | |
339 | |
340 class _HashTableValueIterable<V> extends Iterable<V> { | |
341 final _HashTable _hashTable; | |
342 final int _entryIndex; | |
343 _HashTableValueIterable(this._hashTable, this._entryIndex); | |
344 | |
345 Iterator<V> get iterator => | |
346 new _HashTableValueIterator<V>(_hashTable, _entryIndex); | |
347 | |
348 int get length => _hashTable._elementCount; | |
349 } | |
350 | |
351 /** | |
352 * Iterator traversing a [HashTable] and returning a value for each entry. | |
353 */ | |
354 class _HashTableValueIterator<V> implements Iterator<V> { | |
355 final _HashTable _hashTable; | |
356 /** The index of the value in the entry.*/ | |
357 final int _entryIndex; | |
358 final int _modificationCount; | |
359 /** Location right after last found element. */ | |
360 int _offset = 0; | |
361 V _current = null; | |
362 | |
363 _HashTableValueIterator(_HashTable hashTable, this._entryIndex) | |
364 : _hashTable = hashTable, | |
365 _modificationCount = hashTable._modificationCount { | |
366 assert(_entryIndex > 0); // Use key-iterator above for index 0. | |
367 assert(_entryIndex < hashTable._entrySize); | |
368 } | |
369 | |
370 bool moveNext() { | |
371 _hashTable._checkModification(_modificationCount); | |
372 | |
373 List table = _hashTable._table; | |
374 int entrySize = _hashTable._entrySize; | |
375 | |
376 while (_offset < table.length) { | |
377 Object entry = table[_offset]; | |
378 int currentOffset = _offset; | |
379 _offset += entrySize; | |
380 if (!_hashTable._isFree(entry)) { | |
381 _current = table[currentOffset + _entryIndex]; | |
382 return true; | |
383 } | |
384 } | |
385 _current = null; | |
386 return false; | |
387 } | |
388 | |
389 V get current => _current; | |
390 } | |
OLD | NEW |