3cf5d6531d
BUG= Review URL: https://chromiumcodereview.appspot.com//10890030 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@11606 260f80e4-7a28-3924-810f-c04153c831b5
455 lines
12 KiB
Dart
455 lines
12 KiB
Dart
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// Hash map implementation with open addressing and quadratic probing.
|
|
class HashMapImplementation<K extends Hashable, V> implements HashMap<K, V> {
|
|
|
|
// The [_keys] list contains the keys inserted in the map.
|
|
// The [_keys] list must be a raw list because it
|
|
// will contain both elements of type K, and the [_DELETED_KEY] of type
|
|
// [_DeletedKeySentinel].
|
|
// The alternative of declaring the [_keys] list as of type Object
|
|
// does not work, because the HashSetIterator constructor would fail:
|
|
// HashSetIterator(HashSet<E> set)
|
|
// : _nextValidIndex = -1,
|
|
// _entries = set_._backingMap._keys {
|
|
// _advance();
|
|
// }
|
|
// With K being type int, for example, it would fail because
|
|
// List<Object> is not assignable to type List<int> of entries.
|
|
List _keys;
|
|
|
|
// The values inserted in the map. For a filled entry index in this
|
|
// list, there is always the corresponding key in the [keys_] list
|
|
// at the same entry index.
|
|
List<V> _values;
|
|
|
|
// The load limit is the number of entries we allow until we double
|
|
// the size of the lists.
|
|
int _loadLimit;
|
|
|
|
// The current number of entries in the map. Will never be greater
|
|
// than [_loadLimit].
|
|
int _numberOfEntries;
|
|
|
|
// The current number of deleted entries in the map.
|
|
int _numberOfDeleted;
|
|
|
|
// The sentinel when a key is deleted from the map.
|
|
static final _DeletedKeySentinel _DELETED_KEY = const _DeletedKeySentinel();
|
|
|
|
// The initial capacity of a hash map.
|
|
static final int _INITIAL_CAPACITY = 8; // must be power of 2
|
|
|
|
HashMapImplementation() {
|
|
_numberOfEntries = 0;
|
|
_numberOfDeleted = 0;
|
|
_loadLimit = _computeLoadLimit(_INITIAL_CAPACITY);
|
|
_keys = new List(_INITIAL_CAPACITY);
|
|
_values = new List<V>(_INITIAL_CAPACITY);
|
|
}
|
|
|
|
factory HashMapImplementation.from(Map<K, V> other) {
|
|
Map<K, V> result = new HashMapImplementation<K, V>();
|
|
other.forEach((K key, V value) { result[key] = value; });
|
|
return result;
|
|
}
|
|
|
|
static int _computeLoadLimit(int capacity) {
|
|
return (capacity * 3) ~/ 4;
|
|
}
|
|
|
|
static int _firstProbe(int hashCode, int length) {
|
|
return hashCode & (length - 1);
|
|
}
|
|
|
|
static int _nextProbe(int currentProbe, int numberOfProbes, int length) {
|
|
return (currentProbe + numberOfProbes) & (length - 1);
|
|
}
|
|
|
|
int _probeForAdding(K key) {
|
|
int hash = _firstProbe(key.hashCode(), _keys.length);
|
|
int numberOfProbes = 1;
|
|
int initialHash = hash;
|
|
// insertionIndex points to a slot where a key was deleted.
|
|
int insertionIndex = -1;
|
|
while (true) {
|
|
// [existingKey] can be either of type [K] or [_DeletedKeySentinel].
|
|
Object existingKey = _keys[hash];
|
|
if (existingKey === null) {
|
|
// We are sure the key is not already in the set.
|
|
// If the current slot is empty and we didn't find any
|
|
// insertion slot before, return this slot.
|
|
if (insertionIndex < 0) return hash;
|
|
// If we did find an insertion slot before, return it.
|
|
return insertionIndex;
|
|
} else if (existingKey == key) {
|
|
// The key is already in the map. Return its slot.
|
|
return hash;
|
|
} else if ((insertionIndex < 0) && (_DELETED_KEY === existingKey)) {
|
|
// The slot contains a deleted element. Because previous calls to this
|
|
// method may not have had this slot deleted, we must continue iterate
|
|
// to find if there is a slot with the given key.
|
|
insertionIndex = hash;
|
|
}
|
|
|
|
// We did not find an insertion slot. Look at the next one.
|
|
hash = _nextProbe(hash, numberOfProbes++, _keys.length);
|
|
// _ensureCapacity has guaranteed the following cannot happen.
|
|
// assert(hash != initialHash);
|
|
}
|
|
}
|
|
|
|
int _probeForLookup(K key) {
|
|
int hash = _firstProbe(key.hashCode(), _keys.length);
|
|
int numberOfProbes = 1;
|
|
int initialHash = hash;
|
|
while (true) {
|
|
// [existingKey] can be either of type [K] or [_DeletedKeySentinel].
|
|
Object existingKey = _keys[hash];
|
|
// If the slot does not contain anything (in particular, it does not
|
|
// contain a deleted key), we know the key is not in the map.
|
|
if (existingKey === null) return -1;
|
|
// The key is in the map, return its index.
|
|
if (existingKey == key) return hash;
|
|
// Go to the next probe.
|
|
hash = _nextProbe(hash, numberOfProbes++, _keys.length);
|
|
// _ensureCapacity has guaranteed the following cannot happen.
|
|
// assert(hash != initialHash);
|
|
}
|
|
}
|
|
|
|
void _ensureCapacity() {
|
|
int newNumberOfEntries = _numberOfEntries + 1;
|
|
// Test if adding an element will reach the load limit.
|
|
if (newNumberOfEntries >= _loadLimit) {
|
|
_grow(_keys.length * 2);
|
|
return;
|
|
}
|
|
|
|
// Make sure that we don't have poor performance when a map
|
|
// contains lots of deleted entries: we _grow if
|
|
// there are more deleted entried than free entries.
|
|
int capacity = _keys.length;
|
|
int numberOfFreeOrDeleted = capacity - newNumberOfEntries;
|
|
int numberOfFree = numberOfFreeOrDeleted - _numberOfDeleted;
|
|
// assert(numberOfFree > 0);
|
|
if (_numberOfDeleted > numberOfFree) {
|
|
_grow(_keys.length);
|
|
}
|
|
}
|
|
|
|
static bool _isPowerOfTwo(int x) {
|
|
return ((x & (x - 1)) == 0);
|
|
}
|
|
|
|
void _grow(int newCapacity) {
|
|
assert(_isPowerOfTwo(newCapacity));
|
|
int capacity = _keys.length;
|
|
_loadLimit = _computeLoadLimit(newCapacity);
|
|
List oldKeys = _keys;
|
|
List<V> oldValues = _values;
|
|
_keys = new List(newCapacity);
|
|
_values = new List<V>(newCapacity);
|
|
for (int i = 0; i < capacity; i++) {
|
|
// [key] can be either of type [K] or [_DeletedKeySentinel].
|
|
Object key = oldKeys[i];
|
|
// If there is no key, we don't need to deal with the current slot.
|
|
if (key === null || key === _DELETED_KEY) {
|
|
continue;
|
|
}
|
|
V value = oldValues[i];
|
|
// Insert the {key, value} pair in their new slot.
|
|
int newIndex = _probeForAdding(key);
|
|
_keys[newIndex] = key;
|
|
_values[newIndex] = value;
|
|
}
|
|
_numberOfDeleted = 0;
|
|
}
|
|
|
|
void clear() {
|
|
_numberOfEntries = 0;
|
|
_numberOfDeleted = 0;
|
|
int length = _keys.length;
|
|
for (int i = 0; i < length; i++) {
|
|
_keys[i] = null;
|
|
_values[i] = null;
|
|
}
|
|
}
|
|
|
|
void operator []=(K key, V value) {
|
|
_ensureCapacity();
|
|
int index = _probeForAdding(key);
|
|
if ((_keys[index] === null) || (_keys[index] === _DELETED_KEY)) {
|
|
_numberOfEntries++;
|
|
}
|
|
_keys[index] = key;
|
|
_values[index] = value;
|
|
}
|
|
|
|
V operator [](K key) {
|
|
int index = _probeForLookup(key);
|
|
if (index < 0) return null;
|
|
return _values[index];
|
|
}
|
|
|
|
V putIfAbsent(K key, V ifAbsent()) {
|
|
int index = _probeForLookup(key);
|
|
if (index >=0) return _values[index];
|
|
|
|
V value = ifAbsent();
|
|
this[key] = value;
|
|
return value;
|
|
}
|
|
|
|
V remove(K key) {
|
|
int index = _probeForLookup(key);
|
|
if (index >= 0) {
|
|
_numberOfEntries--;
|
|
V value = _values[index];
|
|
_values[index] = null;
|
|
// Set the key to the sentinel to not break the probing chain.
|
|
_keys[index] = _DELETED_KEY;
|
|
_numberOfDeleted++;
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool isEmpty() {
|
|
return _numberOfEntries == 0;
|
|
}
|
|
|
|
int get length() {
|
|
return _numberOfEntries;
|
|
}
|
|
|
|
void forEach(void f(K key, V value)) {
|
|
int length = _keys.length;
|
|
for (int i = 0; i < length; i++) {
|
|
var key = _keys[i];
|
|
if ((key !== null) && (key !== _DELETED_KEY)) {
|
|
f(key, _values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Collection<K> getKeys() {
|
|
List<K> list = new List<K>(length);
|
|
int i = 0;
|
|
forEach(void _(K key, V value) {
|
|
list[i++] = key;
|
|
});
|
|
return list;
|
|
}
|
|
|
|
Collection<V> getValues() {
|
|
List<V> list = new List<V>(length);
|
|
int i = 0;
|
|
forEach(void _(K key, V value) {
|
|
list[i++] = value;
|
|
});
|
|
return list;
|
|
}
|
|
|
|
bool containsKey(K key) {
|
|
return (_probeForLookup(key) != -1);
|
|
}
|
|
|
|
bool containsValue(V value) {
|
|
int length = _values.length;
|
|
for (int i = 0; i < length; i++) {
|
|
var key = _keys[i];
|
|
if ((key !== null) && (key !== _DELETED_KEY)) {
|
|
if (_values[i] == value) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String toString() {
|
|
return Maps.mapToString(this);
|
|
}
|
|
}
|
|
|
|
class HashSetImplementation<E extends Hashable> implements HashSet<E> {
|
|
|
|
HashSetImplementation() {
|
|
_backingMap = new HashMapImplementation<E, E>();
|
|
}
|
|
|
|
factory HashSetImplementation.from(Iterable<E> other) {
|
|
Set<E> set = new HashSetImplementation<E>();
|
|
for (final e in other) {
|
|
set.add(e);
|
|
}
|
|
return set;
|
|
}
|
|
|
|
void clear() {
|
|
_backingMap.clear();
|
|
}
|
|
|
|
void add(E value) {
|
|
_backingMap[value] = value;
|
|
}
|
|
|
|
bool contains(E value) {
|
|
return _backingMap.containsKey(value);
|
|
}
|
|
|
|
bool remove(E value) {
|
|
if (!_backingMap.containsKey(value)) return false;
|
|
_backingMap.remove(value);
|
|
return true;
|
|
}
|
|
|
|
void addAll(Collection<E> collection) {
|
|
collection.forEach(void _(E value) {
|
|
add(value);
|
|
});
|
|
}
|
|
|
|
Set<E> intersection(Collection<E> collection) {
|
|
Set<E> result = new Set<E>();
|
|
collection.forEach(void _(E value) {
|
|
if (contains(value)) result.add(value);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool isSubsetOf(Collection<E> other) {
|
|
return new Set<E>.from(other).containsAll(this);
|
|
}
|
|
|
|
void removeAll(Collection<E> collection) {
|
|
collection.forEach(void _(E value) {
|
|
remove(value);
|
|
});
|
|
}
|
|
|
|
bool containsAll(Collection<E> collection) {
|
|
return collection.every(bool _(E value) {
|
|
return contains(value);
|
|
});
|
|
}
|
|
|
|
void forEach(void f(E element)) {
|
|
_backingMap.forEach(void _(E key, E value) {
|
|
f(key);
|
|
});
|
|
}
|
|
|
|
Set map(f(E element)) {
|
|
Set result = new Set();
|
|
_backingMap.forEach(void _(E key, E value) {
|
|
result.add(f(key));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
Dynamic reduce(Dynamic initialValue,
|
|
Dynamic combine(Dynamic previousValue, E element)) {
|
|
return Collections.reduce(this, initialValue, combine);
|
|
}
|
|
|
|
Set<E> filter(bool f(E element)) {
|
|
Set<E> result = new Set<E>();
|
|
_backingMap.forEach(void _(E key, E value) {
|
|
if (f(key)) result.add(key);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool every(bool f(E element)) {
|
|
Collection<E> keys = _backingMap.getKeys();
|
|
return keys.every(f);
|
|
}
|
|
|
|
bool some(bool f(E element)) {
|
|
Collection<E> keys = _backingMap.getKeys();
|
|
return keys.some(f);
|
|
}
|
|
|
|
bool isEmpty() {
|
|
return _backingMap.isEmpty();
|
|
}
|
|
|
|
int get length() {
|
|
return _backingMap.length;
|
|
}
|
|
|
|
Iterator<E> iterator() {
|
|
return new HashSetIterator<E>(this);
|
|
}
|
|
|
|
String toString() {
|
|
return Collections.collectionToString(this);
|
|
}
|
|
|
|
// The map backing this set. The associations in this map are all
|
|
// of the form element -> element. If a value is not in the map,
|
|
// then it is not in the set.
|
|
HashMapImplementation<E, E> _backingMap;
|
|
}
|
|
|
|
class HashSetIterator<E> implements Iterator<E> {
|
|
|
|
// TODO(4504458): Replace set_ with set.
|
|
HashSetIterator(HashSetImplementation<E> set_)
|
|
: _nextValidIndex = -1,
|
|
_entries = set_._backingMap._keys {
|
|
_advance();
|
|
}
|
|
|
|
bool hasNext() {
|
|
if (_nextValidIndex >= _entries.length) return false;
|
|
if (_entries[_nextValidIndex] === HashMapImplementation._DELETED_KEY) {
|
|
// This happens in case the set was modified in the meantime.
|
|
// A modification on the set may make this iterator misbehave,
|
|
// but we should never return the sentinel.
|
|
_advance();
|
|
}
|
|
return _nextValidIndex < _entries.length;
|
|
}
|
|
|
|
E next() {
|
|
if (!hasNext()) {
|
|
throw const NoMoreElementsException();
|
|
}
|
|
E res = _entries[_nextValidIndex];
|
|
_advance();
|
|
return res;
|
|
}
|
|
|
|
void _advance() {
|
|
int length = _entries.length;
|
|
var entry;
|
|
final deletedKey = HashMapImplementation._DELETED_KEY;
|
|
do {
|
|
if (++_nextValidIndex >= length) break;
|
|
entry = _entries[_nextValidIndex];
|
|
} while ((entry === null) || (entry === deletedKey));
|
|
}
|
|
|
|
// The entries in the set. May contain null or the sentinel value.
|
|
List<E> _entries;
|
|
|
|
// The next valid index in [_entries] or the length of [entries_].
|
|
// If it is the length of [_entries], calling [hasNext] on the
|
|
// iterator will return false.
|
|
int _nextValidIndex;
|
|
}
|
|
|
|
/**
|
|
* A singleton sentinel used to represent when a key is deleted from the map.
|
|
* We can't use [: const Object() :] as a sentinel because it would end up
|
|
* canonicalized and then we cannot distinguish the deleted key from the
|
|
* canonicalized [: Object() :].
|
|
*/
|
|
class _DeletedKeySentinel {
|
|
const _DeletedKeySentinel();
|
|
}
|