Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: sdk/lib/collection/hash_table.dart

Issue 12213010: New implementation of {,Linked}Hash{Set,Map}. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Made null elements work again, added tests for null. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
11
12 const _TOMBSTONE = const _DeadEntry();
13 const _NULL = const _NullKey();
14
15 class _HashTable<K> {
16 /**
17 * Table of entries with [_entrySize] slots per entry.
18 *
19 * Capacity in entries must be factor of two.
20 */
21 List _table;
22 /** Current capacity. Always equal to [:_table.length ~/ _entrySize:]. */
23 int _capacity;
24 /** Count of occupied entries, including deleted ones. */
25 int _entryCount = 0;
26 /** Count of deleted entries. */
27 int _deletedCount = 0;
28 /** Counter incremented when table is modified. */
29 int _modificationCount = 0;
30
31 _HashTable(int initialCapacity) : _capacity = initialCapacity {
32 _table = _createTable(initialCapacity);
33 }
34
35 /** Reads key from table. Converts _NULL marker to null. */
36 Object _key(offset) {
37 assert(!_isFree(_table[offset]));
38 Object key = _table[offset];
39 if (!identical(key, _NULL)) return key;
40 return null;
41 }
42
43 /** Writes key to table. Converts null to _NULL marker. */
44 void _setKey(int offset, Object key) {
45 if (key == null) key = _NULL;
46 _table[offset] = key;
47 }
48
49 int get _elementCount => _entryCount - _deletedCount;
50
51 /** Size of each entry. */
52 int get _entrySize => 1;
53
54 void _checkModification(int expectedModificationCount) {
55 if (_modificationCount != expectedModificationCount) {
56 throw new ConcurrentModificationError(this);
57 }
58 }
59
60 void _recordModification() {
61 // Value cycles after 2^30 modifications. If you keep hold of an
62 // iterator for that long, you might miss a modification detection,
63 // and iteration can go sour. Don't do that.
64 _modificationCount = (_modificationCount + 1) & (0x3FFFFFFF);
65 }
66
67 /**
68 * Create an empty table.
69 */
70 List _createTable(int capacity) {
71 List table = new List.fixedLength(capacity * _entrySize);
72 return table;
73 }
74
75 /** First table probe. */
76 int _firstProbe(int hashCode, int capacity) {
77 return hashCode & (capacity - 1);
78 }
79
80 /** Following table probes. */
81 int _nextProbe(int previousIndex, int probeCount, int capacity) {
82 return (previousIndex + probeCount) & (capacity - 1);
83 }
84
85 /** Whether an object is a free-marker (either tombstone or free). */
86 bool _isFree(Object marker) =>
87 marker == null || identical(marker, _TOMBSTONE);
88
89 /**
90 * Look up the offset for an object in the table.
91 *
92 * Finds the offset of the object in the table, if it is there,
93 * or the first free offset for its hashCode.
94 */
95 int _probeForAdd(int hashCode, Object object) {
96 int entrySize = _entrySize;
97 int index = _firstProbe(hashCode, _capacity);
98 int firstTombstone = -1;
99 int probeCount = 0;
100 while (true) {
101 int offset = index * entrySize;
102 Object entry = _table[offset];
103 if (identical(entry, _TOMBSTONE)) {
104 if (firstTombstone < 0) firstTombstone = offset;
105 } else if (entry == null) {
106 if (firstTombstone < 0) return offset;
107 return firstTombstone;
108 } else if (identical(_NULL, entry) ? _equals(null, object)
109 : _equals(entry, object)) {
110 return offset;
111 }
112 // The _nextProbe is designed so that it hits
113 // every index eventually.
114 index = _nextProbe(index, ++probeCount, _capacity);
115 }
116 }
117
118 /**
119 * Look up the offset for an object in the table.
120 *
121 * If the object is in the table, its offset is returned.
122 *
123 * If the object is not in the table, Otherwise a negative value is returned.
124 */
125 int _probeForLookup(int hashCode, Object object) {
126 int entrySize = _entrySize;
127 int index = _firstProbe(hashCode, _capacity);
128 int probeCount = 0;
129 while (true) {
130 int offset = index * entrySize;
131 Object entry = _table[offset];
132 if (entry == null) {
133 return -1;
134 } else if (identical(_NULL, entry) ? _equals(null, object)
135 : _equals(entry, object)) {
136 // If entry is _TOMBSTONE, it matches nothing.
137 // Consider special casing it to make 'equals' calls monomorphic.
138 return offset;
139 }
140 // The _nextProbe is designed so that it hits
141 // every index eventually.
142 index = _nextProbe(index, ++probeCount, _capacity);
143 }
144 }
145
146 // Override the following two to change equality/hashCode computations
147
148 /**
149 * Compare two object for equality.
150 *
151 * The first object is the one already in the table,
152 * and the second is the one being searched for.
153 */
154 bool _equals(Object element, Object other) {
155 return element == other;
156 }
157
158 /**
159 * Compute hash-code for an object.
160 */
161 int _hashCodeOf(Object object) => object.hashCode;
162
163 /**
164 * Ensure that the table isn't too full for its own good.
165 *
166 * Call this after adding an element.
167 */
168 int _checkCapacity() {
169 // Compute everything in multiples of entrySize to avoid division.
170 int freeCount = _capacity - _entryCount;
171 if (freeCount * 4 < _capacity ||
172 freeCount < _deletedCount) {
173 // Less than 25% free or more deleted entries than free entries.
174 _grow(_entryCount - _deletedCount);
175 }
176 }
177
178 void _grow(int contentCount) {
179 int capacity = _capacity;
180 // Don't grow to less than twice the needed capacity.
181 int minCapacity = contentCount * 2;
182 while (capacity < minCapacity) {
183 capacity *= 2;
184 }
185 // Reset to another table and add all existing elements.
186 List oldTable = _table;
187 _table = _createTable(capacity);
188 _capacity = capacity;
189 _entryCount = 0;
190 _deletedCount = 0;
191 _addAllEntries(oldTable);
192 _recordModification();
193 }
194
195 /**
196 * Copies all non-free entries from the old table to the new empty table.
197 */
198 void _addAllEntries(List oldTable) {
199 for (int i = 0; i < oldTable.length; i += _entrySize) {
200 Object object = oldTable[i];
201 if (!_isFree(object)) {
202 int toOffset = _put(object);
203 _copyEntry(oldTable, i, toOffset);
204 }
205 }
206 }
207
208 /**
209 * Copies everything but the key element from one entry to another.
210 *
211 * Called while growing the base array.
212 *
213 * Override this if any non-key fields need copying.
214 */
215 void _copyEntry(List fromTable, int fromOffset, int toOffset) {}
216
217 // The following three methods are for simple get/set/remove operations.
218 // They only affect the key of an entry. The remaining fields must be
219 // filled by the caller.
220
221 /**
222 * Returns the offset of a key in [_table], or negative if it's not there.
223 */
224 int _get(K key) {
225 return _probeForLookup(_hashCodeOf(key), key);
226 }
227
228 /**
229 * Puts the key into the table and returns its offset into [_table].
230 *
231 * If [_entrySize] is greater than 1, the caller should fill the
232 * remaining fields.
233 *
234 * Remember to call [_checkCapacity] after using this method.
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 /**
286 * Generic iterable based on a [_HashTable].
287 */
288 abstract class _HashTableIterable<E> extends Iterable<E> {
289 final _HashTable _hashTable;
290 _HashTableIterable(this._hashTable);
291
292 Iterator<E> get iterator;
293
294 /**
295 * Return the iterated value for a given entry.
296 */
297 E _valueAt(int offset, Object key);
298
299 int get length => _hashTable._elementCount;
300
301 /**
302 * Iterates over non-free entries of the table.
303 *
304 * I
305 */
306 void forEach(void action(E element)) {
307 int entrySize = _hashTable._entrySize;
308 List table = _hashTable._table;
309 int modificationCount = _hashTable._modificationCount;
310 for (int offset = 0; offset < table.length; offset += entrySize) {
311 Object entry = table[offset];
312 if (!_hashTable._isFree(entry)) {
313 E value = _valueAt(offset, entry);
314 action(value);
315 }
316 _hashTable._checkModification(modificationCount);
317 }
318 }
319
320 bool get isEmpty => _hashTable._elementCount == 0;
321
322 E get single {
323 if (_hashTable._elementCount > 1) {
324 throw new StateError("More than one element");
325 }
326 return first;
327 }
328 }
329
330 abstract class _HashTableIterator<E> implements Iterator<E> {
331 final _HashTable _hashTable;
332 final int _modificationCount;
333 /** Location right after last found element. */
334 int _offset = 0;
335 E _current = null;
336
337 _HashTableIterator(_HashTable hashTable)
338 : _hashTable = hashTable,
339 _modificationCount = hashTable._modificationCount;
340
341 bool moveNext() {
342 _hashTable._checkModification(_modificationCount);
343
344 List table = _hashTable._table;
345 int entrySize = _hashTable._entrySize;
346
347 while (_offset < table.length) {
348 int currentOffset = _offset;
349 Object entry = table[currentOffset];
350 _offset = currentOffset + entrySize;
351 if (!_hashTable._isFree(entry)) {
352 _current = _valueAt(currentOffset, entry);
353 return true;
354 }
355 }
356 _current = null;
357 return false;
358 }
359
360 E get current => _current;
361
362 E _valueAt(int offset, Object key);
363 }
364
365 class _HashTableKeyIterable<K> extends _HashTableIterable<K> {
366 _HashTableKeyIterable(_HashTable<K> hashTable) : super(hashTable);
367
368 Iterator<K> get iterator => new _HashTableKeyIterator<K>(_hashTable);
369
370 K _valueAt(int offset, Object key) {
371 if (identical(key, _NULL)) return null;
372 return key;
373 }
374
375 bool contains(Object value) => _hashTable._get(value) >= 0;
376 }
377
378 class _HashTableKeyIterator<K> extends _HashTableIterator<K> {
379 _HashTableKeyIterator(_HashTable hashTable) : super(hashTable);
380
381 K _valueAt(int offset, Object key) {
382 if (identical(key, _NULL)) return null;
383 return key;
384 }
385 }
386
387 class _HashTableValueIterable<V> extends _HashTableIterable<V> {
388 final int _entryIndex;
389
390 _HashTableValueIterable(_HashTable hashTable, this._entryIndex)
391 : super(hashTable);
392
393 Iterator<V> get iterator {
394 return new _HashTableValueIterator<V>(_hashTable, _entryIndex);
395 }
396
397 V _valueAt(int offset, Object key) => _hashTable._table[offset + _entryIndex];
398 }
399
400 class _HashTableValueIterator<V> extends _HashTableIterator<V> {
401 final int _entryIndex;
402
403 _HashTableValueIterator(_HashTable hashTable, this._entryIndex)
404 : super(hashTable);
405
406 V _valueAt(int offset, Object key) => _hashTable._table[offset + _entryIndex];
407 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698