Hash

A Hash is an unordered collection of key-value pairs. It can be thought of as a table that maps ‘key’ objects (of which there are no duplicates within a Hash) to ‘value’ objects (of which there may be duplicates). This implementation is close to Ruby’s Hash class, though you may be familiar with the data structure in some other form; Java’s HashMap, Python dictionaries, JavaScript objects, PHP’s associative arrays and Scheme’s alists all perform a similar function.

// In the browser
JS.require('JS.Hash', function(Hash) { ... });

// In CommonJS
var Hash = require('jsclass/src/hash').Hash;

JavaScript’s native Object class could be considered a basic kind of hashtable in which the keys must be strings. This class provides a more general-purpose implementation with many helpful methods not provided by JavaScript. The keys in a Hash may be numbers, strings, or any object that implements the equals() and hash() methods.

For our examples we’re going to use two classes with pretty trivial equality operations. Note how hash() uses the same data as equals() ensuring correct behaviour:

State = new Class({
    initialize: function(name, code) {
        this.name = name;
        this.code = code;
    },
    equals: function(other) {
        return (other instanceof this.klass) &&
               other.code === this.code;
    },
    hash: function() {
        return this.code;
    }
});

Senator = new Class({
    initialize: function(name) {
        this.name = name;
    },
    equals: function(other) {
        return (other instanceof this.klass) &&
               other.name === this.name;
    },
    hash: function() {
        return this.name;
    }
});

And we’ll instantiate a few pieces of data to put in a Hash:

var NY = new State('New York', 'NY'),
    CA = new State('California', 'CA'),
    IL = new State('Illinois', 'IL'),
    TX = new State('Texas', 'TX'),
    VA = new State('Virginia', 'VA'),

    hutchinson = new Senator('Kay Bailey Hutchison'),
    burris     = new Senator('Roland Burris'),
    feinstein  = new Senator('Dianne Feinstein'),
    gillibrand = new Senator('Kirsten Gillibrand'),
    hancock    = new Senator('John Hancock');

Instantiating a Hash

There are three ways to instantiate a Hash. The first is to simply list the key-value pairs as an array. Retrieving a key will then return the corresponding value:

var senators = new Hash([
    NY,   gillibrand,
    CA,   feinstein,
    IL,   burris,
    TX,   hutchinson
]);

senators.get(IL).name   // -> "Roland Burris"

One important function of a Hash is that you don’t need the original key object to retrieve its associated value, you just need some object equal to the key. States are compared using their code, so we could create another object to represent Texas to get its senator:

senators.get(new State('Lone Star State', 'TX'))
// -> #<Senator name="Kay Bailey Hutchison">

The second way is to instantiate the Hash using a single default value; this value will then be returned when you ask for a key the hash doesn’t have:

var senators = new Hash(hancock);
senators.get(NY).name   // -> "John Hancock"

The third and final way is to instantiate using a function, which will be called when a nonexistent key is accessed. The function is passed the hash and the requested key, so you can store the result in the hash if required:

var senators = new Hash(function(hash, key) {
    var result = new Senator('The senator for ' + key.name
                                                + ' (' + key.code + ')');
    hash.store(key, result);
    return result;
});

senators.size           // -> 0
senators.get(CA).name   // -> "The senator for California (CA)" 
senators.size           // -> 1

Enumeration

Hashes are Enumerable, and their forEach() method yields a Hash.Pair object with each iteration. Each pair has a key and a value. Iteration order is not guaranteed, though on many JavaScript implementations you may find insertion order is preserved. Do not rely on order when using a Hash. For example:

var senators = new Hash([
    NY,   gillibrand,
    CA,   feinstein,
    IL,   burris,
    TX,   hutchinson
]);

senators.forEach(function(pair) {
    console.log(pair.key.code + ': ' + pair.value.name);
});

// Prints:
// NY: Kirsten Gillibrand
// CA: Dianne Feinstein
// IL: Roland Burris
// TX: Kay Bailey Hutchison

The hash package also contains a class called OrderedHash. It has the same API as Hash but keeps its keys in insertion order at all times.

The instance methods of Hash are as follows:

assoc(key)

Returns the Hash.Pair object corresponding to the given key, or null if so such key is found.

senators.assoc(NY).key.code     // -> "NY" 
senators.assoc(IL).value.name   // -> "Roland Burris" 
senators.assoc(VA)              // -> null

rassoc(value)

Returns the first matching Hash.Pair object for to the given value, or null if so such value is found.

senators.rassoc(feinstein).key.code   // -> "CA" 
senators.rassoc(burris).value.name    // -> "Roland Burris" 
senators.rassoc(hancock)              // -> null

clear()

Removes all the key-value pairs from the hash.

senators.clear();
senators.size       // -> 0
senators.get(TX)    // -> null

compareByIdentity()

Instructs the hash to use the === identity operator instead of the equals() method to compare keys. Values must then be retrieved using the same key object as was used to store the value initially.

comparesByIdentity()

Returns true iff the hash is using the === operator rather than the equals() method to compare keys.

setDefault(value)

Sets the default value for the hash to the given value. The default value is returned whenever a nonexistent key is accessed using get(), remove() or shift(). The value may be a function, in which case it is called with the hash and the accessed key and the resulting value is returned.

senators.get('foo');    // -> null

senators.setDefault(hancock);
senators.get('foo')     // -> #<Senator name="John Hancock">

senators.setDefault(function(hash, key) {
    return new Senator('Senator for ' + key.code);
});
senators.get(VA)        // -> #<Senator name="Senator for VA">

getDefault(key)

Returns the default value for the hash, or null if none is set. The key is only used if the default value is a function (see setDefault()).

equals(other)

Returns true iff other is a hash containing the same data (using equality semantics) as the receiver.

fetch(key, defaultValue)

This is similar to get(key), but allows you to override the default value of the hash using defaultValue. If defaultValue is a function it is called with only the key as an argument. The the key is not found and no defaultValue is given, an error is thrown.

// Assume no default value

senators.fetch(CA)        // -> #<Senator name="Dianne Feinstein">
senators.fetch('foo')     // -> Error: key not found

senators.fetch('foo', hancock)  // -> #<Senator name="John Hancock">

senators.fetch('foo', function(key) {
    return new Senator(key.toUpperCase());
});
// -> #<Senator name="FOO">

forEachKey(block, context)

Iterates over the keys in the hash, yielding the key each time. The optional parameter context sets the binding of this within the block.

senators.forEachKey(function(key) {
    // key is a State
});

forEachPair(block, context)

Iterates over the pairs in the hash, yielding the key and value each time. The optional parameter context sets the binding of this within the block.

senators.forEachPair(function(key, value) {
    // key is a State, value is a Senator
});

forEachValue(block, context)

Iterates over the values in the hash, yielding the value each time. The optional parameter context sets the binding of this within the block.

senators.forEachValue(function(value) {
    // value is a Senator
});

get(key)

Returns the value corresponding to the given key. If the key is not found, the default value for the key is returned (see setDefault()). If no default value exists, null is returned.

hasKey(key)

Returns true iff the hash contains the given key. Aliased as includes().

hasValue(value)

Returns true iff the hash contains the given value.

includes(key)

Alias for kasKey().

index(value)

Alias for key().

invert()

Returns a new hash created by using the hash’s values as keys, and the keys as values.

isEmpty()

Returns true iff the hash contains no data.

keepIf(predicate, context)

Deletes all the pairs that do not satisfy the predicate from the hash. context sets the binding of this within the predicate function. For example:

// Remove pairs for California and Illinois
senators.keepIf(function(pair) { return pair.key.code < 'M' });

// senators is now:
// { CA => feinstein, IL => burris }

key(value)

Returns the first key from the hash whose corresponding value is value. Aliased as index().

senators.key(gillibrand);
// -> #<State code="NY" name="New York">

keys()

Returns an array containing all the keys from the hash.

merge(other, block, context)

Returns a new hash containing all the key-value pairs from both the receiver and other. If a key exists in both hashes, the optional block parameter is used to pick which value to keep. If no block is given, values from other overwrite values from the receiver. See Hash#update() for more information.

put(key, value)

Alias for store().

rehash()

Call this if the state of any key changes such that its hashcode changes. This reindexes the hash and makes sure all pairs are in the correct buckets.

remove(key, block)

Deletes the given key from the hash and returns the corresponding value. If the key is not found, the default value (see setDefault()) is returned. If the key is not found and the optional function block is passed, the result of calling block with the key is returned.

var h = new Hash([ 'a',100, 'b',200 ]);
h.remove('a')   // -> 100
h.remove('z')   // -> null

h.remove('z', function(el) { return el + ' not found' })
// -> "z not found"

removeIf(predicate, context)

Deletes all the pairs that satisfy the predicate from the hash. context sets the binding of this within the predicate function. For example:

// Remove pairs for California and Illinois
senators.removeIf(function(pair) { return pair.key.code < 'M' });

// senators is now:
// { NY => gillibrand, TX => hutchinson }

replace(other)

Removes all existing key-value pairs from the receiver and replaces them with the contents of the hash other.

shift()

Removes a single key-value pair from the hash and returns it, or returns the hash’s default value if it is already empty.

var h = new Hash(50);
h.store('a', 100);
h.store('b', 200);

h.shift()   // -> #<Pair key="a" value=100>
h.shift()   // -> #<Pair key="b" value=200>
h.shift()   // -> 50

store(key, value)

Associates the given key with the given value in the receiving hash. If the state of key changes causing a change to its hashcode, call rehash() on the hash to reindex it. Aliased as put().

update(other, block, context)

Modifies the hash using the key-value pairs from the other hash, overwriting pairs with duplicate keys with the values from other. If the optional block is passed, it can be used to decide which value to keep for duplicate keys. The optional context parameter sets the binding of this within block.

var h = new Hash([ 'a',1, 'b',2, 'c',3 ]),
    g = new Hash([ 'a',5, 'b',0 ]);

h.update(g, function(key, oldVal, newVal) {
    return oldVal > newVal ? oldVal : newVal;
});

// h is now { 'a' => 5, 'b' => 2, 'c' => 3 }

values()

Returns an array containing all the values from the hash.

valuesAt(key1 [, key2 ...])

Returns an array of values corresponding to the given list of keys.