Files
narya/src/as/com/threerings/util/HashMap.as
T
Michael Bayne 6e9077780c Added/updated source headers.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5829 542714f4-19e9-0310-aa3c-eee0fc999fb1
2009-06-20 21:09:34 +00:00

353 lines
11 KiB
ActionScript

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2009 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.util {
import flash.errors.IllegalOperationError;
import flash.utils.Dictionary;
/**
* An implementation of a HashMap in actionscript. Any object (and null) may
* be used as a key. Simple keys (Number, int, uint, Boolean, String) utilize
* a Dictionary internally for storage; keys that implement Hashable are
* stored efficiently, and any other key can also be used if the equalsFn
* and hashFn are specified to the constructor.
*/
public class HashMap
implements Map
{
/**
* Construct a HashMap
*
* @param loadFactor - A measure of how full the hashtable is allowed to
* get before it is automatically resized. The default
* value of 1.75 should be fine.
* @param equalsFn - (Optional) A function to use to compare object
* equality for keys that are neither simple nor
* implement Hashable. The signature should be
* "function (o1, o2) :Boolean".
* @param hashFn - (Optional) A function to use to generate a hash
* code for keys that are neither simple nor
* implement Hashable. The signature should be
* "function (obj) :*", where the return type is
* numeric or String. Two objects that are equals
* according to the specified equalsFn <b>must</b>
* generate equal values when passed to the hashFn.
*/
public function HashMap (
loadFactor :Number = 1.75,
equalsFn :Function = null,
hashFn :Function = null)
{
if ((equalsFn != null) != (hashFn != null)) {
throw new ArgumentError("Both the equals and hash functions " +
"must be specified, or neither.");
}
_loadFactor = loadFactor;
_equalsFn = equalsFn;
_hashFn = hashFn;
}
/** @inheritDoc */
public function clear () :void
{
_simpleData = null;
_simpleSize = 0;
_entries = null;
_entriesSize = 0;
}
/** @inheritDoc */
public function containsKey (key :Object) :Boolean
{
return (undefined !== get(key));
}
/** @inheritDoc */
public function get (key :Object) :*
{
if (isSimple(key)) {
return (_simpleData == null) ? undefined : _simpleData[key];
}
if (_entries == null) {
return undefined;
}
var hkey :Hashable = keyFor(key);
var hash :int = hkey.hashCode();
var index :int = indexFor(hash);
var e :HashMap_Entry = (_entries[index] as HashMap_Entry);
while (e != null) {
if (e.hash == hash && e.key.equals(hkey)) {
return e.value;
}
e = e.next;
}
return undefined;
}
/** @inheritDoc */
public function isEmpty () :Boolean
{
return (size() == 0);
}
/** @inheritDoc */
public function put (key :Object, value :Object) :*
{
var oldValue :*;
if (isSimple(key)) {
if (_simpleData == null) {
_simpleData = new Dictionary();
}
oldValue = _simpleData[key];
_simpleData[key] = value;
if (oldValue === undefined) {
_simpleSize++;
}
return oldValue;
}
// lazy-create the array holding other hashables
if (_entries == null) {
_entries = [];
_entries.length = DEFAULT_BUCKETS;
}
var hkey :Hashable = keyFor(key);
var hash :int = hkey.hashCode();
var index :int = indexFor(hash);
var firstEntry :HashMap_Entry = (_entries[index] as HashMap_Entry);
for (var e :HashMap_Entry = firstEntry; e != null; e = e.next) {
if (e.hash == hash && e.key.equals(hkey)) {
oldValue = e.value;
e.value = value;
return oldValue; // size did not change
}
}
_entries[index] = new HashMap_Entry(hash, hkey, value, firstEntry);
_entriesSize++;
// check to see if we should grow the map
if (_entriesSize > _entries.length * _loadFactor) {
resize(2 * _entries.length);
}
// indicate that there was no value previously stored for the key
return undefined;
}
/** @inheritDoc */
public function remove (key :Object) :*
{
if (isSimple(key)) {
if (_simpleData == null) {
return undefined;
}
var oldValue :* = _simpleData[key];
if (oldValue !== undefined) {
_simpleSize--;
}
delete _simpleData[key];
return oldValue;
}
if (_entries == null) {
return undefined;
}
var hkey :Hashable = keyFor(key);
var hash :int = hkey.hashCode();
var index :int = indexFor(hash);
var prev :HashMap_Entry = (_entries[index] as HashMap_Entry);
var e :HashMap_Entry = prev;
while (e != null) {
var next :HashMap_Entry = e.next;
if (e.hash == hash && e.key.equals(hkey)) {
if (prev == e) {
_entries[index] = next;
} else {
prev.next = next;
}
_entriesSize--;
// check to see if we should shrink the map
if ((_entries.length > DEFAULT_BUCKETS) &&
(_entriesSize < _entries.length * _loadFactor * .125)) {
resize(Math.max(DEFAULT_BUCKETS, _entries.length / 2));
}
return e.value;
}
prev = e;
e = next;
}
return undefined; // never found
}
/** @inheritDoc */
public function size () :int
{
return _simpleSize + _entriesSize;
}
/** @inheritDoc */
public function keys () :Array
{
var keys :Array = [];
forEach0(function (k :*, v :*) :void {
keys.push(k);
});
return keys;
}
/** @inheritDoc */
public function values () :Array
{
var vals :Array = [];
forEach0(function (k :*, v :*) :void {
vals.push(v);
});
return vals;
}
/** @inheritDoc */
public function forEach (fn :Function) :void
{
forEach0(fn);
}
/**
* Internal forEach.
* @private
*/
protected function forEach0 (fn :Function) :void
{
if (_simpleData != null) {
for (var key :Object in _simpleData) {
fn(key, _simpleData[key]);
}
}
if (_entries != null) {
for (var ii :int = _entries.length - 1; ii >= 0; ii--) {
for (var e :HashMap_Entry = (_entries[ii] as HashMap_Entry); e != null;
e = e.next) {
fn(e.getOriginalKey(), e.value);
}
}
}
}
/**
* Return a Hashable that represents the key.
* @private
*/
protected function keyFor (key :Object) :Hashable
{
if (key is Hashable) {
return (key as Hashable);
} else if (_hashFn == null) {
throw new IllegalOperationError("Illegal key specified " +
"for HashMap created without hashing functions.");
} else {
return new HashMap_KeyWrapper(key, _equalsFn, _hashFn);
}
}
/**
* Return an index for the specified hashcode.
* @private
*/
protected function indexFor (hash :int) :int
{
// TODO: improve?
return Math.abs(hash) % _entries.length;
}
/**
* Return true if the specified key may be used to store values in a
* Dictionary object.
* @private
*/
protected function isSimple (key :Object) :Boolean
{
return (key == null) || (key is String) || (key is Number) || (key is Boolean) ||
(key is Enum);
}
/**
* Resize the entries with Hashable keys to optimize
* the memory/performance tradeoff.
* @private
*/
protected function resize (newSize :int) :void
{
var oldEntries :Array = _entries;
_entries = [];
_entries.length = newSize;
// place all the old entries in the new map
for (var ii :int = 0; ii < oldEntries.length; ii++) {
var e :HashMap_Entry = (oldEntries[ii] as HashMap_Entry);
while (e != null) {
var next :HashMap_Entry = e.next;
var index :int = indexFor(e.hash);
e.next = (_entries[index] as HashMap_Entry);
_entries[index] = e;
e = next;
}
}
}
/** The current number of key/value pairs stored in the Dictionary. @private */
protected var _simpleSize :int = 0;
/** The current number of key/value pairs stored in the _entries. @private */
protected var _entriesSize :int = 0;
/** The load factor. @private */
protected var _loadFactor :Number;
/** If non-null, contains simple key/value pairs. @private */
protected var _simpleData :Dictionary
/** If non-null, contains Hashable keys and their values. @private */
protected var _entries :Array;
/** The hashing function to use for non-Hashable complex keys. @private */
protected var _hashFn :Function;
/** The equality function to use for non-Hashable complex keys. @private */
protected var _equalsFn :Function;
/** The default size for the bucketed hashmap. @private */
protected static const DEFAULT_BUCKETS :int = 16;
}
} // end: package com.threerings.util