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

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: Address review comments, fix bugs. 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 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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698