Skip to content

Medium/oid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

oid

Build Status

This Node module provides a simple utility for object identity hashing and two related classes. This can be useful any time you need to do triple-equals (===) style comparisons across arbitrary numbers of objects. Instead of doing an O(N^2) set of comparisons, you can instead get the identity hashes of the things you want to compare, and use those in clever ways to whittle down the required comparisons, often allowing O(1) implementations.

Building and Installing

npm install oid

Or grab the source and

node-waf configure build

Note: This module contains native code, and so you will have to have a C compiler available. Consult your OS documentation for details on setting that up.

Testing

npm test

Or

node ./test/test.js

Example

This example (also available in the example directory) deduplicates the elements of an array in O(N) time (instead of O(N^2)), by taking advantage of an identity set.

function dedup(orig) {
    var result = [];
    var elements = oid.createSet();

    for (var i = 0; i < orig.length; i++) {
        var one = orig[i];
        if (elements.add(one)) {
            result.push(one);
        }
    }

    return result;
}

Here's a transcript of a use of this function as well as the more fundamental hash() function. Note that the literal syntax [] creates a new object with a distinct identity each time it is executed.

> x = []
[]
> y = []
[]
> z = y
[]
> x === y
false
> oid.hash(x) === oid.hash(y)
false
> z === y
true
> oid.hash(z) === oid.hash(y)
true
> a = dedup([x, y, x, x, y, y, 1, 2, 3, 1, 2, 3, x, y, "yumminess"])
[ [], [], 1, 2, 3, 'yumminess' ]
> a[0] === x
true
> a[1] === y
true

Usage

This library provides one regular function and two constructors.

Top-Level Functions

oid.hash(value)

Return an identity hash of the given value or object. The return value from two calls to this function are guaranteed to be the same when given the same input, where "same" is the relationship defined by the triple-equals (===) operator of JavaScript.

The return value is furthermore guaranteed to be a positive (non-negative and non-zero) integer value.

The return value is never guaranteed to be unique across multiple values. That is, there are many pairs of values or objects that are not the same but which do have the same identity hash. Somewhat more mathematically: hash(x) !== hash(y) implies that x !== y, but hash(x) === hash(y) does not imply that x === y.

For normal objects (including arrays, functions, and regular expressions), this returns an arbitrary internally-generated id number.

For strings, this returns a hash based on the characters contained in the string. The algorithm used is similar to (but not quite identical to) that used by String.hashCode() in Java.

For numbers, this returns a hash based on the numeric value. More specifically, it is produced by inspecting the underlying byte representation of the value. As such, it is not guaranteed to be stable across different implementations of JavaScript.

For booleans, null, and undefined, this returns a particular predefined value (different for each), which are all prime numbers representable with five digits in base ten.

idmap = oid.createMap()

See below.

idset = oid.createSet()

See below.

Identity Maps

An identity map is a set of key-value associations, where the keys are arbitrary objects or values, compared by identity.

idmap = oid.createMap()

This constructs and returns a new identity map.

idmap.get(key, ifNotFound) => value

Get the value associated with the given key. If there is no mapping for the key, return the ifNotFound argument (which defaults to undefined).

idmap.set(key, value, ifNotFound) => previousValue

Set the value associated with the given key to the given value, and return the previously associated value. If there was no previous mapping for the key, return the ifNotFound argument (which defaults to undefined).

idmap.has(key) => boolean

Return true if there is a mapping for the given key or false if not.

idmap.remove(key, ifNotFound) => previousValue

Remove the mapping for the given key, returning its formerly associated value or the ifNotFound value if the key wasn't formerly mapped.

idmap.size() => int

Get the number of elements in the map.

idmap.forEach(callback)

Call the given callback as callback(key, value) for each association in the map. There is no guarantee about what order the callbacks will be made in.

Identity Sets

An identity set is a set (unordered list of unique elements) of objects / values, where set membership is determined by identity comparison.

idset = oid.createSet()

This constructs and returns a new identity set.

idset.has(value) => boolean

Return true if there the given value is in the set or false if not.

idset.add(value) => boolean

Add the given value to the set. Returns true if this operation actually changed the set (that is, true if the item wasn't already in the set).

idset.remove(value) => boolean

Remove the given value from the set. Returns true if this operation actually changed the set (that is, true if the item in fact was in the set to begin with).

idset.size() => int

Get the number of elements in the set.

idset.forEach(callback)

Call the given callback as callback(value) for each element of the set. There is no guarantee about what order the callbacks will be made in.

To Do

  • The two collection classes probably ought to have more iteration methods.

  • A weak-key identity map class and a weak-contents identity set class would probably be handy.

  • It seems reasonable that this module provide efficient implementations of deep variants of clone() and isEqual().

Contributing

Questions, comments, bug reports, and pull requests are all welcome. Submit them at the project on GitHub.

Bug reports that include steps-to-reproduce (including code) are the best. Even better, make them in the form of pull requests that update the test suite. Thanks!

Author

Dan Bornstein (personal website), supported by The Obvious Corporation.

License

Copyright 2012 The Obvious Corporation.

Licensed under the Apache License, Version 2.0. See the top-level file LICENSE.txt and (http://www.apache.org/licenses/LICENSE-2.0).