├── .gitignore ├── .travis.yml ├── binding.gyp ├── example └── dedup.js ├── package.json ├── src └── oidNative.cc ├── test └── test.js ├── README.md ├── lib └── oid.js └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | oidNative.node 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.12.0" 4 | - "0.12" 5 | - "0.10" 6 | 7 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'oidNative', 5 | 'sources': [ 'src/oidNative.cc' ], 6 | 'include_dirs': ["=0.8.0" 40 | }, 41 | "dependencies": { 42 | "nan": "^2.12.1" 43 | }, 44 | "scripts": { 45 | "install": "node-gyp configure build; mkdir -p bin; mv build/Release/*.node bin", 46 | "test": "node test/test.js" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/oidNative.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Obvious Corporation. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace v8; 8 | 9 | NAN_METHOD(ObjectIdHash) { 10 | Local val = info[0]; 11 | if (val->IsNull()) { 12 | info.GetReturnValue().Set(Nan::New(99961)); // A prime number. 13 | 14 | } else { 15 | Local obj = val->ToObject(); 16 | if (obj.IsEmpty()) { 17 | return Nan::ThrowError("Not an object."); 18 | } 19 | 20 | int hash = obj->GetIdentityHash() & 0x7fffffff; 21 | 22 | if (hash == 0) { 23 | // V8 guarantees the original hash is non-zero, but it doesn't 24 | // guarantee that it's not MININT. We want to guarantee a positive 25 | // number (that is non-zero and non-negative), and so we have to 26 | // deal with this case specially. 27 | hash = 1; 28 | } 29 | 30 | info.GetReturnValue().Set(Nan::New(hash)); 31 | } 32 | } 33 | 34 | NAN_METHOD(NumberIdHash) { 35 | Local num = info[0]->ToNumber(); 36 | if (num.IsEmpty()) { 37 | return Nan::ThrowError("Not a number."); 38 | } 39 | 40 | union { 41 | double doubleValue; 42 | unsigned char buffer[sizeof(double)]; 43 | } hood; 44 | 45 | hood.doubleValue = num->Value(); 46 | 47 | int hash = 56081; // A prime number. 48 | for (size_t i = 0; i < sizeof(double); i++) { 49 | hash = (hash * 31) + hood.buffer[i]; 50 | } 51 | 52 | hash &= 0x7fffffff; 53 | 54 | if (hash == 0) { 55 | // Guarantee non-zero. 56 | hash = 1; 57 | } 58 | 59 | info.GetReturnValue().Set(Nan::New((double) hash)); 60 | } 61 | 62 | void init(Handle target) { 63 | target->Set(Nan::New("objectIdHash").ToLocalChecked(), 64 | Nan::New(ObjectIdHash)->GetFunction()); 65 | target->Set(Nan::New("numberIdHash").ToLocalChecked(), 66 | Nan::New(NumberIdHash)->GetFunction()); 67 | } 68 | 69 | NODE_MODULE(oidNative, init) 70 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Obvious Corporation. 2 | 3 | /* 4 | * Tests of oid 5 | */ 6 | 7 | /* 8 | * Modules used 9 | */ 10 | 11 | "use strict"; 12 | 13 | var assert = require("assert"); 14 | var util = require("util"); 15 | 16 | var oid = require("../lib/oid"); 17 | 18 | 19 | /* 20 | * Helper functions 21 | */ 22 | 23 | /** 24 | * Verify that the given value is valid as an id hash. 25 | */ 26 | function validate(hash) { 27 | if (typeof hash !== "number") { 28 | throw new Error("Not a number: " + hash); 29 | } 30 | 31 | if (hash !== (hash & 0x7fffffff)) { 32 | throw new Error("Not in range: " + hash); 33 | } 34 | } 35 | 36 | /* 37 | * Tests 38 | */ 39 | 40 | function testHashBasics() { 41 | var hash = oid.hash; 42 | 43 | assert.equal(hash(null), 99961); 44 | assert.equal(hash(undefined), 99971); 45 | assert.equal(hash(false), 99991); 46 | assert.equal(hash(true), 99989); 47 | 48 | assert.equal(hash(""), 1); 49 | assert.equal(hash("Black Forest Cake"), 824715674); 50 | assert.equal(hash("biscuits"), 1082698090); 51 | assert.equal(hash("croissants"), 112234901); 52 | assert.equal(hash("muffins"), 1400606342); 53 | assert.equal(hash("pie"), 110988); 54 | assert.equal(hash("popovers"), 667514478); 55 | assert.equal(hash("scones"), 1239713053); 56 | 57 | // Note: We can't count on numbers hashing to anything in particular. 58 | for (var i = 0; i < 1000; i++) { 59 | validate(hash(-i)); 60 | validate(hash(i)); 61 | validate(hash(i * 1.23e45)); 62 | } 63 | 64 | // Just make sure that all the usual sub-types of object can be hashed. 65 | validate(hash([])); 66 | validate(hash([1, 2])); 67 | validate(hash({})); 68 | validate(hash({a: 1, b: 2})); 69 | validate(hash(/Stand back!/)); 70 | validate(hash(function x() { return x; })); 71 | } 72 | 73 | function testMapBasics() { 74 | var map = oid.createMap(); 75 | 76 | assert.equal(map.get(1), undefined); 77 | assert.equal(map.get(1, "blort"), "blort"); 78 | assert.equal(map.has(1), false); 79 | assert.equal(map.has("yo"), false); 80 | assert.equal(map.has([]), false); 81 | assert.equal(map.size(), 0); 82 | 83 | assert.equal(map.set("foo", "bar"), undefined); 84 | assert.equal(map.get("foo"), "bar"); 85 | assert.equal(map.has("foo"), true); 86 | assert.equal(map.size(), 1); 87 | 88 | assert.equal(map.set(1, "zorch", "xyz"), "xyz"); 89 | assert.equal(map.get(1), "zorch"); 90 | assert.equal(map.get(1, "blort"), "zorch"); 91 | assert.equal(map.set(1, "spaz"), "zorch"); 92 | assert.equal(map.get(1), "spaz"); 93 | assert.equal(map.size(), 2); 94 | 95 | var x = {}; 96 | assert.equal(map.set(x, "fizmo"), undefined); 97 | assert.equal(map.get(x), "fizmo"); 98 | assert.equal(map.get({}), undefined); 99 | assert.equal(map.has(x), true); 100 | assert.equal(map.has({}), false); 101 | assert.equal(map.size(), 3); 102 | assert.equal(map.remove({}), undefined); 103 | assert.equal(map.has(x), true); 104 | assert.equal(map.remove({}, "igram"), "igram"); 105 | assert.equal(map.remove(x), "fizmo"); 106 | assert.equal(map.has(x), false); 107 | assert.equal(map.size(), 2); 108 | 109 | map.set(undefined, "hi"); 110 | assert.equal(map.get(undefined), "hi"); 111 | 112 | x = [1, 2, 3]; 113 | map.set(x, undefined); 114 | assert.equal(map.has(x), true); 115 | assert.equal(map.get(x), undefined); 116 | assert.equal(map.get(x, "aha"), undefined); 117 | } 118 | 119 | function testSetBasics() { 120 | var set = oid.createSet(); 121 | 122 | assert.equal(set.has(1), false); 123 | assert.equal(set.has("blort"), false); 124 | assert.equal(set.has([]), false); 125 | assert.equal(set.has(false), false); 126 | assert.equal(set.has(null), false); 127 | assert.equal(set.has(undefined), false); 128 | assert.equal(set.has(true), false); 129 | assert.equal(set.size(), 0); 130 | 131 | assert.equal(set.add("x"), true); 132 | assert.equal(set.add("x"), false); 133 | assert.equal(set.has("x"), true); 134 | assert.equal(set.size(), 1); 135 | assert.equal(set.remove("x"), true); 136 | assert.equal(set.size(), 0); 137 | assert.equal(set.remove("x"), false); 138 | assert.equal(set.has("x"), false); 139 | assert.equal(set.size(), 0); 140 | 141 | assert.equal(set.add(undefined), true); 142 | assert.equal(set.has(undefined), true); 143 | 144 | assert.equal(set.add(1.23), true); 145 | assert.equal(set.has(1.23), true); 146 | 147 | var x = {}; 148 | assert.equal(set.add(x), true); 149 | assert.equal(set.has(x), true); 150 | assert.equal(set.has({}), false); 151 | assert.equal(set.remove({}), false); 152 | assert.equal(set.has(x), true); 153 | assert.equal(set.remove(x), true); 154 | assert.equal(set.has(x), false); 155 | } 156 | 157 | function testMap_forEach() { 158 | var pairs = [ 159 | { k: [], v: "empty one" }, 160 | { k: [], v: "empty two" }, 161 | { k: [], v: "empty three" }, 162 | { k: [], v: "empty four" }, 163 | { k: [], v: "empty five" }, 164 | { k: 10, v: "ten" }, 165 | { k: {a: 1}, v: "a-one" }, 166 | { k: [1,2,3], v: 123 } 167 | ]; 168 | 169 | var map = oid.createMap(); 170 | pairs.forEach(function (p) { map.set(p.k, p.v); }); 171 | 172 | function killPair(k, v) { 173 | for (var i = 0; i < pairs.length; i++) { 174 | var one = pairs[i]; 175 | if ((one.k === k) && (one.v === v)) { 176 | pairs.splice(i, 1); 177 | return; 178 | } 179 | } 180 | throw new Error(util.format("Did not find:", k, v)); 181 | } 182 | 183 | map.forEach(killPair); 184 | assert.equal(pairs.length, 0); 185 | } 186 | 187 | function testSet_forEach() { 188 | var elements = [ 189 | [], [], [], [], [], [1,2,3], [1,2,3], {a:1}, {a:1}, 190 | "foo", 2.4, 4.8 191 | ]; 192 | 193 | var set = oid.createSet(); 194 | elements.forEach(function (e) { set.add(e); }); 195 | 196 | function killElement(v) { 197 | for (var i = 0; i < elements.length; i++) { 198 | if (elements[i] === v) { 199 | elements.splice(i, 1); 200 | return; 201 | } 202 | } 203 | throw new Error(util.format("Did not find:", v)); 204 | } 205 | 206 | set.forEach(killElement); 207 | assert.equal(elements.length, 0); 208 | } 209 | 210 | testHashBasics(); 211 | testMapBasics(); 212 | testSetBasics(); 213 | testMap_forEach(); 214 | testSet_forEach(); 215 | 216 | console.log("All tests pass!"); 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | oid 2 | === 3 | 4 | [![Build Status](https://travis-ci.org/Medium/oid.svg)](https://travis-ci.org/Medium/oid) 5 | 6 | This Node module provides a simple utility for object identity hashing 7 | and two related classes. This can be useful any time you need to do 8 | triple-equals (`===`) style comparisons across arbitrary numbers of 9 | objects. Instead of doing an O(N^2) set of comparisons, you can 10 | instead get the identity hashes of the things you want to compare, and 11 | use those in clever ways to whittle down the required comparisons, 12 | often allowing O(1) implementations. 13 | 14 | 15 | Building and Installing 16 | ----------------------- 17 | 18 | ```shell 19 | npm install oid 20 | ``` 21 | 22 | Or grab the source and 23 | 24 | ```shell 25 | node-waf configure build 26 | ``` 27 | 28 | **Note**: This module contains native code, and so you will have 29 | to have a C compiler available. Consult your OS documentation for 30 | details on setting that up. 31 | 32 | 33 | Testing 34 | ------- 35 | 36 | ```shell 37 | npm test 38 | ``` 39 | 40 | Or 41 | 42 | ```shell 43 | node ./test/test.js 44 | ``` 45 | 46 | Example 47 | ------- 48 | 49 | This example (also available in the example directory) deduplicates 50 | the elements of an array in O(N) time (instead of O(N^2)), by taking 51 | advantage of an identity set. 52 | 53 | ```javascript 54 | function dedup(orig) { 55 | var result = []; 56 | var elements = oid.createSet(); 57 | 58 | for (var i = 0; i < orig.length; i++) { 59 | var one = orig[i]; 60 | if (elements.add(one)) { 61 | result.push(one); 62 | } 63 | } 64 | 65 | return result; 66 | } 67 | ``` 68 | 69 | Here's a transcript of a use of this function as well as the more 70 | fundamental `hash()` function. Note that the literal syntax `[]` 71 | creates a new object with a distinct identity each time it is 72 | executed. 73 | 74 | ``` 75 | > x = [] 76 | [] 77 | > y = [] 78 | [] 79 | > z = y 80 | [] 81 | > x === y 82 | false 83 | > oid.hash(x) === oid.hash(y) 84 | false 85 | > z === y 86 | true 87 | > oid.hash(z) === oid.hash(y) 88 | true 89 | > a = dedup([x, y, x, x, y, y, 1, 2, 3, 1, 2, 3, x, y, "yumminess"]) 90 | [ [], [], 1, 2, 3, 'yumminess' ] 91 | > a[0] === x 92 | true 93 | > a[1] === y 94 | true 95 | ``` 96 | 97 | Usage 98 | ----- 99 | 100 | This library provides one regular function and two constructors. 101 | 102 | Top-Level Functions 103 | ------------------- 104 | 105 | ### oid.hash(value) 106 | 107 | Return an identity hash of the given value or object. The return value 108 | from two calls to this function are guaranteed to be the same when 109 | given the same input, where "same" is the relationship defined by the 110 | triple-equals (`===`) operator of JavaScript. 111 | 112 | The return value is furthermore guaranteed to be a positive 113 | (non-negative and non-zero) integer value. 114 | 115 | The return value is *never* guaranteed to be unique across multiple 116 | values. That is, there are many pairs of values or objects that are 117 | not the same but which *do* have the same identity hash. Somewhat 118 | more mathematically: `hash(x) !== hash(y)` implies that `x !== y`, but 119 | `hash(x) === hash(y)` does not imply that `x === y`. 120 | 121 | For normal objects (including arrays, functions, and regular 122 | expressions), this returns an arbitrary internally-generated id 123 | number. 124 | 125 | For strings, this returns a hash based on the characters contained in 126 | the string. The algorithm used is similar to (but not quite identical 127 | to) that used by `String.hashCode()` in Java. 128 | 129 | For numbers, this returns a hash based on the numeric value. More specifically, 130 | it is produced by inspecting the underlying byte representation of the 131 | value. As such, it is not guaranteed to be stable across different 132 | implementations of JavaScript. 133 | 134 | For booleans, `null`, and `undefined`, this returns a particular 135 | predefined value (different for each), which are all prime numbers 136 | representable with five digits in base ten. 137 | 138 | ### idmap = oid.createMap() 139 | 140 | See below. 141 | 142 | ### idset = oid.createSet() 143 | 144 | See below. 145 | 146 | 147 | Identity Maps 148 | ------------- 149 | 150 | An identity map is a set of key-value associations, where the 151 | keys are arbitrary objects or values, compared by identity. 152 | 153 | ### idmap = oid.createMap() 154 | 155 | This constructs and returns a new identity map. 156 | 157 | ### idmap.get(key, ifNotFound) => value 158 | 159 | Get the value associated with the given key. If there is no mapping 160 | for the key, return the `ifNotFound` argument (which defaults to 161 | `undefined`). 162 | 163 | ### idmap.set(key, value, ifNotFound) => previousValue 164 | 165 | Set the value associated with the given key to the given value, and 166 | return the previously associated value. If there was no previous 167 | mapping for the key, return the `ifNotFound` argument (which defaults 168 | to `undefined`). 169 | 170 | ### idmap.has(key) => boolean 171 | 172 | Return `true` if there is a mapping for the given key or `false` 173 | if not. 174 | 175 | ### idmap.remove(key, ifNotFound) => previousValue 176 | 177 | Remove the mapping for the given key, returning its formerly 178 | associated value or the `ifNotFound` value if the key wasn't formerly 179 | mapped. 180 | 181 | ### idmap.size() => int 182 | 183 | Get the number of elements in the map. 184 | 185 | ### idmap.forEach(callback) 186 | 187 | Call the given callback as `callback(key, value)` for each association 188 | in the map. There is no guarantee about what order the callbacks will 189 | be made in. 190 | 191 | Identity Sets 192 | ------------- 193 | 194 | An identity set is a set (unordered list of unique elements) of 195 | objects / values, where set membership is determined by identity 196 | comparison. 197 | 198 | ### idset = oid.createSet() 199 | 200 | This constructs and returns a new identity set. 201 | 202 | ### idset.has(value) => boolean 203 | 204 | Return `true` if there the given value is in the set or `false` if not. 205 | 206 | ### idset.add(value) => boolean 207 | 208 | Add the given value to the set. Returns `true` if this operation 209 | actually changed the set (that is, `true` if the item wasn't already 210 | in the set). 211 | 212 | ### idset.remove(value) => boolean 213 | 214 | Remove the given value from the set. Returns `true` if this operation 215 | actually changed the set (that is, `true` if the item in fact was in 216 | the set to begin with). 217 | 218 | ### idset.size() => int 219 | 220 | Get the number of elements in the set. 221 | 222 | ### idset.forEach(callback) 223 | 224 | Call the given callback as `callback(value)` for each element 225 | of the set. There is no guarantee about what order the callbacks will 226 | be made in. 227 | 228 | To Do 229 | ----- 230 | 231 | * The two collection classes probably ought to have more iteration methods. 232 | 233 | * A weak-key identity map class and a weak-contents identity set class 234 | would probably be handy. 235 | 236 | * It seems reasonable that this module provide efficient implementations 237 | of deep variants of `clone()` and `isEqual()`. 238 | 239 | 240 | Contributing 241 | ------------ 242 | 243 | Questions, comments, bug reports, and pull requests are all welcome. 244 | Submit them at [the project on GitHub](https://github.com/Obvious/oid/). 245 | 246 | Bug reports that include steps-to-reproduce (including code) are the 247 | best. Even better, make them in the form of pull requests that update 248 | the test suite. Thanks! 249 | 250 | Author 251 | ------ 252 | 253 | [Dan Bornstein](https://github.com/danfuzz) 254 | ([personal website](http://www.milk.com/)), supported by 255 | [The Obvious Corporation](http://obvious.com/). 256 | 257 | License 258 | ------- 259 | 260 | Copyright 2012 [The Obvious Corporation](http://obvious.com/). 261 | 262 | Licensed under the Apache License, Version 2.0. 263 | See the top-level file `LICENSE.txt` and 264 | (http://www.apache.org/licenses/LICENSE-2.0). 265 | -------------------------------------------------------------------------------- /lib/oid.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Obvious Corporation. 2 | 3 | /* 4 | * "oid": Utilities for object identity 5 | */ 6 | 7 | /* 8 | * Modules used 9 | */ 10 | 11 | "use strict"; 12 | 13 | var oidNative = require("../bin/oidNative"); 14 | 15 | 16 | /* 17 | * Helper functions 18 | */ 19 | 20 | /** 21 | * String hash, similar to Java's definition. 22 | */ 23 | function stringHash(string) { 24 | var hash = 0; 25 | var length = string.length; 26 | 27 | for (var i = 0; i < length; i++) { 28 | hash = ((hash * 31) + string.charCodeAt(i)) & 0x7fffffff; 29 | } 30 | 31 | return (hash === 0) ? 1 : hash; 32 | } 33 | 34 | 35 | /* 36 | * Exported bindings 37 | */ 38 | 39 | /** 40 | * Construct a set (unordered list of objects) keyed by object/value 41 | * identity. 42 | */ 43 | function createSet() { 44 | var items = {}; // map from hash to array of objects/values 45 | var count = 0; 46 | 47 | /** 48 | * Get the number of elements in the set. 49 | */ 50 | function size() { 51 | return count; 52 | } 53 | 54 | /** 55 | * Return true if there the given value is in the set or false if not. 56 | */ 57 | function has(value) { 58 | var candidates = items[hash(value)]; 59 | 60 | if (!candidates) { 61 | return false; 62 | } 63 | 64 | for (var i = candidates.length - 1; i >= 0; i--) { 65 | if (value === candidates[i]) { 66 | return true; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | 73 | /** 74 | * Add the given value to the set. Returns true if this operation 75 | * actually changed the set (that is, true if the item wasn't already 76 | * in the set). 77 | */ 78 | function add(value) { 79 | var h = hash(value); 80 | var candidates = items[h]; 81 | 82 | if (!candidates) { 83 | candidates = items[h] = []; 84 | } 85 | 86 | for (var i = candidates.length - 1; i >= 0; i--) { 87 | if (value === candidates[i]) { 88 | return false; // Already in set. 89 | } 90 | } 91 | 92 | candidates.push(value); 93 | count++; 94 | return true; 95 | } 96 | 97 | /** 98 | * Remove the given value from the set. Returns true if this operation 99 | * actually changed the set (that is, true if the item in fact was in 100 | * the set to begin with). 101 | */ 102 | function remove(value) { 103 | var candidates = items[hash(value)]; 104 | 105 | if (!candidates) { 106 | return false; // Not present in set. 107 | } 108 | 109 | for (var i = candidates.length - 1; i >= 0; i--) { 110 | if (value === candidates[i]) { 111 | candidates.splice(i, 1); 112 | count--; 113 | return true; 114 | } 115 | } 116 | 117 | return false; // Not present in set. 118 | } 119 | 120 | /** 121 | * Call the given callback on each element. The callback is called 122 | * with a single argument (the element). There is no guarantee 123 | * about the order of callbacks. 124 | */ 125 | function forEach(callback) { 126 | Object.keys(items).forEach(forHash); 127 | 128 | function forHash(hash) { 129 | items[hash].forEach(callback); 130 | } 131 | } 132 | 133 | return { 134 | add: add, 135 | forEach: forEach, 136 | has: has, 137 | remove: remove, 138 | size: size 139 | }; 140 | } 141 | 142 | /** 143 | * Construct a map (association list) keyed by object/value identity. 144 | */ 145 | function createMap() { 146 | var mappings = {}; // map from hash to array of {key, value} bindings. 147 | var count = 0; 148 | 149 | /** 150 | * Get the number of elements in the map. 151 | */ 152 | function size() { 153 | return count; 154 | } 155 | 156 | /** 157 | * Get the value associated with the given key. If there is 158 | * no mapping for the key, return the ifNotFound argument (which 159 | * defaults to undefined). 160 | */ 161 | function get(key, ifNotFound) { 162 | var candidates = mappings[hash(key)]; 163 | 164 | if (!candidates) { 165 | return ifNotFound; 166 | } 167 | 168 | for (var i = candidates.length - 1; i >= 0; i--) { 169 | var one = candidates[i]; 170 | if (one.key === key) { 171 | return one.value; 172 | } 173 | } 174 | 175 | return ifNotFound; 176 | } 177 | 178 | /** 179 | * Set the value associated with the given key to the given value, 180 | * and return the previously associated value. If there was no 181 | * previous mapping for the key, return the ifNotFound argument 182 | * (which defaults to undefined). 183 | */ 184 | function set(key, value, ifNotFound) { 185 | var h = hash(key); 186 | var candidates = mappings[h]; 187 | 188 | if (!candidates) { 189 | candidates = mappings[h] = []; 190 | } 191 | 192 | for (var i = candidates.length - 1; i >= 0; i--) { 193 | var one = candidates[i]; 194 | if (one.key === key) { 195 | var result = one.value; 196 | one.value = value; 197 | return result; 198 | } 199 | } 200 | 201 | candidates.push({ key: key, value: value }); 202 | count++; 203 | return ifNotFound; 204 | } 205 | 206 | /** 207 | * Return true if there is a mapping for the given key or false 208 | * if not. 209 | */ 210 | function has(key) { 211 | // Use the internal variable "mappings" as the ifNotFound 212 | // value, since we know it can't appear in the map. 213 | return get(key, mappings) !== mappings; 214 | } 215 | 216 | /** 217 | * Remove the mapping for the given key, returning its formerly 218 | * associated value or the ifNotFound value if the key wasn't 219 | * formerly mapped. 220 | */ 221 | function remove(key, ifNotFound) { 222 | var candidates = mappings[hash(key)]; 223 | 224 | if (!candidates) { 225 | return ifNotFound; 226 | } 227 | 228 | for (var i = candidates.length - 1; i >= 0; i--) { 229 | var one = candidates[i]; 230 | if (one.key === key) { 231 | var result = one.value; 232 | candidates.splice(i, 1); 233 | count--; 234 | return result; 235 | } 236 | } 237 | 238 | return ifNotFound; 239 | } 240 | 241 | /** 242 | * Call the given callback on each mapping. The callback is called 243 | * with two arguments, (key, value). There is no guarantee about the 244 | * order of callbacks. 245 | */ 246 | function forEach(callback) { 247 | Object.keys(mappings).forEach(forHash); 248 | 249 | function forHash(hash) { 250 | var arr = mappings[hash]; 251 | for (var i = arr.length - 1; i >= 0; i--) { 252 | var one = arr[i]; 253 | callback(one.key, one.value); 254 | } 255 | } 256 | } 257 | 258 | return { 259 | forEach: forEach, 260 | has: has, 261 | get: get, 262 | remove: remove, 263 | set: set, 264 | size: size 265 | }; 266 | } 267 | 268 | /** 269 | * Return the identity hash code of the given value. 270 | */ 271 | function hash(value) { 272 | switch (typeof value) { 273 | case "number": return oidNative.numberIdHash(value); 274 | case "string": return stringHash(value); 275 | case "boolean": return value ? 99989 : 99991; // Two primes. 276 | case "undefined": return 99971; // Likewise. 277 | default: return oidNative.objectIdHash(value); 278 | } 279 | } 280 | 281 | 282 | /* 283 | * Initialization 284 | */ 285 | 286 | module.exports = { 287 | createMap: createMap, 288 | createSet: createSet, 289 | hash: hash 290 | }; 291 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 The Obvious Corporation. 2 | http://obvious.com/ 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | 17 | ------------------------------------------------------------------------- 18 | Apache License 19 | Version 2.0, January 2004 20 | http://www.apache.org/licenses/ 21 | 22 | 23 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 24 | 25 | 1. Definitions. 26 | 27 | "License" shall mean the terms and conditions for use, reproduction, 28 | and distribution as defined by Sections 1 through 9 of this document. 29 | 30 | "Licensor" shall mean the copyright owner or entity authorized by 31 | the copyright owner that is granting the License. 32 | 33 | "Legal Entity" shall mean the union of the acting entity and all 34 | other entities that control, are controlled by, or are under common 35 | control with that entity. For the purposes of this definition, 36 | "control" means (i) the power, direct or indirect, to cause the 37 | direction or management of such entity, whether by contract or 38 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 39 | outstanding shares, or (iii) beneficial ownership of such entity. 40 | 41 | "You" (or "Your") shall mean an individual or Legal Entity 42 | exercising permissions granted by this License. 43 | 44 | "Source" form shall mean the preferred form for making modifications, 45 | including but not limited to software source code, documentation 46 | source, and configuration files. 47 | 48 | "Object" form shall mean any form resulting from mechanical 49 | transformation or translation of a Source form, including but 50 | not limited to compiled object code, generated documentation, 51 | and conversions to other media types. 52 | 53 | "Work" shall mean the work of authorship, whether in Source or 54 | Object form, made available under the License, as indicated by a 55 | copyright notice that is included in or attached to the work 56 | (an example is provided in the Appendix below). 57 | 58 | "Derivative Works" shall mean any work, whether in Source or Object 59 | form, that is based on (or derived from) the Work and for which the 60 | editorial revisions, annotations, elaborations, or other modifications 61 | represent, as a whole, an original work of authorship. For the purposes 62 | of this License, Derivative Works shall not include works that remain 63 | separable from, or merely link (or bind by name) to the interfaces of, 64 | the Work and Derivative Works thereof. 65 | 66 | "Contribution" shall mean any work of authorship, including 67 | the original version of the Work and any modifications or additions 68 | to that Work or Derivative Works thereof, that is intentionally 69 | submitted to Licensor for inclusion in the Work by the copyright owner 70 | or by an individual or Legal Entity authorized to submit on behalf of 71 | the copyright owner. For the purposes of this definition, "submitted" 72 | means any form of electronic, verbal, or written communication sent 73 | to the Licensor or its representatives, including but not limited to 74 | communication on electronic mailing lists, source code control systems, 75 | and issue tracking systems that are managed by, or on behalf of, the 76 | Licensor for the purpose of discussing and improving the Work, but 77 | excluding communication that is conspicuously marked or otherwise 78 | designated in writing by the copyright owner as "Not a Contribution." 79 | 80 | "Contributor" shall mean Licensor and any individual or Legal Entity 81 | on behalf of whom a Contribution has been received by Licensor and 82 | subsequently incorporated within the Work. 83 | 84 | 2. Grant of Copyright License. Subject to the terms and conditions of 85 | this License, each Contributor hereby grants to You a perpetual, 86 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 87 | copyright license to reproduce, prepare Derivative Works of, 88 | publicly display, publicly perform, sublicense, and distribute the 89 | Work and such Derivative Works in Source or Object form. 90 | 91 | 3. Grant of Patent License. Subject to the terms and conditions of 92 | this License, each Contributor hereby grants to You a perpetual, 93 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 94 | (except as stated in this section) patent license to make, have made, 95 | use, offer to sell, sell, import, and otherwise transfer the Work, 96 | where such license applies only to those patent claims licensable 97 | by such Contributor that are necessarily infringed by their 98 | Contribution(s) alone or by combination of their Contribution(s) 99 | with the Work to which such Contribution(s) was submitted. If You 100 | institute patent litigation against any entity (including a 101 | cross-claim or counterclaim in a lawsuit) alleging that the Work 102 | or a Contribution incorporated within the Work constitutes direct 103 | or contributory patent infringement, then any patent licenses 104 | granted to You under this License for that Work shall terminate 105 | as of the date such litigation is filed. 106 | 107 | 4. Redistribution. You may reproduce and distribute copies of the 108 | Work or Derivative Works thereof in any medium, with or without 109 | modifications, and in Source or Object form, provided that You 110 | meet the following conditions: 111 | 112 | (a) You must give any other recipients of the Work or 113 | Derivative Works a copy of this License; and 114 | 115 | (b) You must cause any modified files to carry prominent notices 116 | stating that You changed the files; and 117 | 118 | (c) You must retain, in the Source form of any Derivative Works 119 | that You distribute, all copyright, patent, trademark, and 120 | attribution notices from the Source form of the Work, 121 | excluding those notices that do not pertain to any part of 122 | the Derivative Works; and 123 | 124 | (d) If the Work includes a "NOTICE" text file as part of its 125 | distribution, then any Derivative Works that You distribute must 126 | include a readable copy of the attribution notices contained 127 | within such NOTICE file, excluding those notices that do not 128 | pertain to any part of the Derivative Works, in at least one 129 | of the following places: within a NOTICE text file distributed 130 | as part of the Derivative Works; within the Source form or 131 | documentation, if provided along with the Derivative Works; or, 132 | within a display generated by the Derivative Works, if and 133 | wherever such third-party notices normally appear. The contents 134 | of the NOTICE file are for informational purposes only and 135 | do not modify the License. You may add Your own attribution 136 | notices within Derivative Works that You distribute, alongside 137 | or as an addendum to the NOTICE text from the Work, provided 138 | that such additional attribution notices cannot be construed 139 | as modifying the License. 140 | 141 | You may add Your own copyright statement to Your modifications and 142 | may provide additional or different license terms and conditions 143 | for use, reproduction, or distribution of Your modifications, or 144 | for any such Derivative Works as a whole, provided Your use, 145 | reproduction, and distribution of the Work otherwise complies with 146 | the conditions stated in this License. 147 | 148 | 5. Submission of Contributions. Unless You explicitly state otherwise, 149 | any Contribution intentionally submitted for inclusion in the Work 150 | by You to the Licensor shall be under the terms and conditions of 151 | this License, without any additional terms or conditions. 152 | Notwithstanding the above, nothing herein shall supersede or modify 153 | the terms of any separate license agreement you may have executed 154 | with Licensor regarding such Contributions. 155 | 156 | 6. Trademarks. This License does not grant permission to use the trade 157 | names, trademarks, service marks, or product names of the Licensor, 158 | except as required for reasonable and customary use in describing the 159 | origin of the Work and reproducing the content of the NOTICE file. 160 | 161 | 7. Disclaimer of Warranty. Unless required by applicable law or 162 | agreed to in writing, Licensor provides the Work (and each 163 | Contributor provides its Contributions) on an "AS IS" BASIS, 164 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 165 | implied, including, without limitation, any warranties or conditions 166 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 167 | PARTICULAR PURPOSE. You are solely responsible for determining the 168 | appropriateness of using or redistributing the Work and assume any 169 | risks associated with Your exercise of permissions under this License. 170 | 171 | 8. Limitation of Liability. In no event and under no legal theory, 172 | whether in tort (including negligence), contract, or otherwise, 173 | unless required by applicable law (such as deliberate and grossly 174 | negligent acts) or agreed to in writing, shall any Contributor be 175 | liable to You for damages, including any direct, indirect, special, 176 | incidental, or consequential damages of any character arising as a 177 | result of this License or out of the use or inability to use the 178 | Work (including but not limited to damages for loss of goodwill, 179 | work stoppage, computer failure or malfunction, or any and all 180 | other commercial damages or losses), even if such Contributor 181 | has been advised of the possibility of such damages. 182 | 183 | 9. Accepting Warranty or Additional Liability. While redistributing 184 | the Work or Derivative Works thereof, You may choose to offer, 185 | and charge a fee for, acceptance of support, warranty, indemnity, 186 | or other liability obligations and/or rights consistent with this 187 | License. However, in accepting such obligations, You may act only 188 | on Your own behalf and on Your sole responsibility, not on behalf 189 | of any other Contributor, and only if You agree to indemnify, 190 | defend, and hold each Contributor harmless for any liability 191 | incurred by, or claims asserted against, such Contributor by reason 192 | of your accepting any such warranty or additional liability. 193 | 194 | END OF TERMS AND CONDITIONS 195 | --------------------------------------------------------------------------------