blob: 1beaf2b9aa785770b43daa6fd446f035c46c595e [file] [log] [blame]
// Copyright (c) 2013, 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.
part of dart.collection;
/**
* Hash map version of the [Map] interface. A [HashMap] does not
* provide any guarantees on the order of keys and values in [keys]
* and [values].
*/
abstract class HashMap<K, V> extends Map<K, V> {
/**
* Creates a map with the default implementation.
*/
factory HashMap() => new _HashMapImpl<K, V>();
/**
* Creates a [HashMap] that contains all key value pairs of [other].
*/
factory HashMap.from(Map<K, V> other) => new _HashMapImpl<K, V>.from(other);
}
/**
* Hash map version of the [Map] interface that preserves insertion
* order.
*/
abstract class LinkedHashMap<K, V> extends HashMap<K, V> {
/**
* Creates a map with the default implementation.
*/
factory LinkedHashMap() => new _LinkedHashMapImpl<K, V>();
/**
* Creates a [LinkedHashMap] that contains all key value pairs of [other].
*/
factory LinkedHashMap.from(Map<K, V> other)
=> new _LinkedHashMapImpl<K, V>.from(other);
}
// Hash map implementation with open addressing and quadratic probing.
class _HashMapImpl<K, 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 const _DeletedKeySentinel _DELETED_KEY = const _DeletedKeySentinel();
// The initial capacity of a hash map.
static const int _INITIAL_CAPACITY = 8; // must be power of 2
_HashMapImpl() {
_numberOfEntries = 0;
_numberOfDeleted = 0;
_loadLimit = _computeLoadLimit(_INITIAL_CAPACITY);
_keys = new List.fixedLength(_INITIAL_CAPACITY);
_values = new List<V>.fixedLength(_INITIAL_CAPACITY);
}
factory _HashMapImpl.from(Map<K, V> other) {
Map<K, V> result = new _HashMapImpl<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) {
if (key == null) throw new ArgumentError(null);
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) &&
(identical(existingKey, _DELETED_KEY))) {
// 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) {
if (key == null) throw new ArgumentError(null);
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.fixedLength(newCapacity);
_values = new List<V>.fixedLength(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 || identical(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) || (identical(_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 get isEmpty {
return _numberOfEntries == 0;
}
int get length {
return _numberOfEntries;
}
void forEach(void f(K key, V value)) {
Iterator<int> it = new _HashMapImplIndexIterator(this);
while (it.moveNext()) {
f(_keys[it.current], _values[it.current]);
}
}
Iterable<K> get keys => new _HashMapImplKeyIterable<K>(this);
Iterable<V> get values => new _HashMapImplValueIterable<V>(this);
bool containsKey(K key) {
return (_probeForLookup(key) != -1);
}
bool containsValue(V value) => values.contains(value);
String toString() {
return Maps.mapToString(this);
}
}
class _HashMapImplKeyIterable<E> extends Iterable<E> {
final _HashMapImpl _map;
_HashMapImplKeyIterable(this._map);
Iterator<E> get iterator => new _HashMapImplKeyIterator<E>(_map);
}
class _HashMapImplValueIterable<E> extends Iterable<E> {
final _HashMapImpl _map;
_HashMapImplValueIterable(this._map);
Iterator<E> get iterator => new _HashMapImplValueIterator<E>(_map);
}
abstract class _HashMapImplIterator<E> implements Iterator<E> {
final _HashMapImpl _map;
int _index = -1;
E _current;
_HashMapImplIterator(this._map);
E _computeCurrentFromIndex(int index, List keys, List values);
bool moveNext() {
int length = _map._keys.length;
int newIndex = _index + 1;
while (newIndex < length) {
var key = _map._keys[newIndex];
if ((key != null) && (!identical(key, _HashMapImpl._DELETED_KEY))) {
_current = _computeCurrentFromIndex(newIndex, _map._keys, _map._values);
_index = newIndex;
return true;
}
newIndex++;
}
_index = length;
_current = null;
return false;
}
E get current => _current;
}
class _HashMapImplKeyIterator<E> extends _HashMapImplIterator<E> {
_HashMapImplKeyIterator(_HashMapImpl map) : super(map);
E _computeCurrentFromIndex(int index, List keys, List values) {
return keys[index];
}
}
class _HashMapImplValueIterator<E> extends _HashMapImplIterator<E> {
_HashMapImplValueIterator(_HashMapImpl map) : super(map);
E _computeCurrentFromIndex(int index, List keys, List values) {
return values[index];
}
}
class _HashMapImplIndexIterator extends _HashMapImplIterator<int> {
_HashMapImplIndexIterator(_HashMapImpl map) : super(map);
int _computeCurrentFromIndex(int index, List keys, List values) {
return index;
}
}
/**
* 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();
}
/**
* This class represents a pair of two objects, used by LinkedHashMap
* to store a {key, value} in a list.
*/
class _KeyValuePair<K, V> {
_KeyValuePair(this.key, this.value) {}
final K key;
V value;
}
/**
* A LinkedHashMap is a hash map that preserves the insertion order
* when iterating over the keys or the values. Updating the value of a
* key does not change the order.
*/
class _LinkedHashMapImpl<K, V> implements LinkedHashMap<K, V> {
DoubleLinkedQueue<_KeyValuePair<K, V>> _list;
HashMap<K, DoubleLinkedQueueEntry<_KeyValuePair<K, V>>> _map;
_LinkedHashMapImpl() {
_map = new HashMap<K, DoubleLinkedQueueEntry<_KeyValuePair<K, V>>>();
_list = new DoubleLinkedQueue<_KeyValuePair<K, V>>();
}
factory _LinkedHashMapImpl.from(Map<K, V> other) {
Map<K, V> result = new _LinkedHashMapImpl<K, V>();
other.forEach((K key, V value) { result[key] = value; });
return result;
}
void operator []=(K key, V value) {
if (_map.containsKey(key)) {
_map[key].element.value = value;
} else {
_list.addLast(new _KeyValuePair<K, V>(key, value));
_map[key] = _list.lastEntry();
}
}
V operator [](K key) {
DoubleLinkedQueueEntry<_KeyValuePair<K, V>> entry = _map[key];
if (entry == null) return null;
return entry.element.value;
}
V remove(K key) {
DoubleLinkedQueueEntry<_KeyValuePair<K, V>> entry = _map.remove(key);
if (entry == null) return null;
entry.remove();
return entry.element.value;
}
V putIfAbsent(K key, V ifAbsent()) {
V value = this[key];
if ((this[key] == null) && !(containsKey(key))) {
value = ifAbsent();
this[key] = value;
}
return value;
}
Iterable<K> get keys {
return new MappedIterable<_KeyValuePair<K, V>, K>(
_list, (_KeyValuePair<K, V> entry) => entry.key);
}
Iterable<V> get values {
return new MappedIterable<_KeyValuePair<K, V>, V>(
_list, (_KeyValuePair<K, V> entry) => entry.value);
}
void forEach(void f(K key, V value)) {
_list.forEach((_KeyValuePair<K, V> entry) {
f(entry.key, entry.value);
});
}
bool containsKey(K key) {
return _map.containsKey(key);
}
bool containsValue(V value) {
return _list.any((_KeyValuePair<K, V> entry) {
return (entry.value == value);
});
}
int get length {
return _map.length;
}
bool get isEmpty {
return length == 0;
}
void clear() {
_map.clear();
_list.clear();
}
String toString() {
return Maps.mapToString(this);
}
}