Add a new Hash module
A simple implementation of the ES6 Map proposal, internally done as a hash table, using System.addressOf() to support keys that are arbitrary objects. Should help replacing linear searches in various places around the shell. https://bugzilla.gnome.org/show_bug.cgi?id=685926
This commit is contained in:
parent
d14cf1e80b
commit
daf8be9f78
@ -28,6 +28,7 @@ nobase_dist_js_DATA = \
|
|||||||
misc/extensionUtils.js \
|
misc/extensionUtils.js \
|
||||||
misc/fileUtils.js \
|
misc/fileUtils.js \
|
||||||
misc/gnomeSession.js \
|
misc/gnomeSession.js \
|
||||||
|
misc/hash.js \
|
||||||
misc/history.js \
|
misc/history.js \
|
||||||
misc/jsParse.js \
|
misc/jsParse.js \
|
||||||
misc/loginManager.js \
|
misc/loginManager.js \
|
||||||
|
141
js/misc/hash.js
Normal file
141
js/misc/hash.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const System = imports.system;
|
||||||
|
|
||||||
|
const Params = imports.misc.params;
|
||||||
|
|
||||||
|
// This is an implementation of EcmaScript SameValue algorithm,
|
||||||
|
// which returns true if two values are not observably distinguishable.
|
||||||
|
// It was taken from http://wiki.ecmascript.org/doku.php?id=harmony:egal
|
||||||
|
//
|
||||||
|
// In the future, we may want to use the 'is' operator instead.
|
||||||
|
function _sameValue(x, y) {
|
||||||
|
if (x === y) {
|
||||||
|
// 0 === -0, but they are not identical
|
||||||
|
return x !== 0 || 1 / x === 1 / y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NaN !== NaN, but they are identical.
|
||||||
|
// NaNs are the only non-reflexive value, i.e., if x !== x,
|
||||||
|
// then x is a NaN.
|
||||||
|
// isNaN is broken: it converts its argument to number, so
|
||||||
|
// isNaN("foo") => true
|
||||||
|
return x !== x && y !== y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _hashers = {
|
||||||
|
object: function(o) { return o ? System.addressOf(o) : 'null'; },
|
||||||
|
function: function(f) { return System.addressOf(f); },
|
||||||
|
string: function(s) { return s; },
|
||||||
|
number: function(n) { return String(n); },
|
||||||
|
undefined: function() { return 'undefined'; },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Map is meant to be similar in usage to ES6 Map, which is
|
||||||
|
described at http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets,
|
||||||
|
without requiring more than ES5 + Gjs extensions.
|
||||||
|
|
||||||
|
Known differences from other implementations:
|
||||||
|
Polyfills around the web usually implement HashMaps for
|
||||||
|
primitive values and reversed WeakMaps for object keys,
|
||||||
|
but we want real maps with real O(1) semantics in all cases,
|
||||||
|
and the easiest way is to have different hashers for different
|
||||||
|
types.
|
||||||
|
|
||||||
|
Known differences from the ES6 specification:
|
||||||
|
- Map is a Lang.Class, not a ES6 class, so inheritance,
|
||||||
|
prototype, sealing, etc. work differently.
|
||||||
|
- items(), keys() and values() don't return iterators,
|
||||||
|
they return actual arrays, so they incur a full copy everytime
|
||||||
|
they're called, and they don't see changes if you mutate
|
||||||
|
the table while iterating
|
||||||
|
(admittedly, the ES6 spec is a bit unclear on this, and
|
||||||
|
the reference code would just blow up)
|
||||||
|
*/
|
||||||
|
const Map = new Lang.Class({
|
||||||
|
Name: 'Map',
|
||||||
|
|
||||||
|
_init: function(iterable) {
|
||||||
|
this._pool = { };
|
||||||
|
|
||||||
|
if (iterable) {
|
||||||
|
for (let i = 0; i < iterable.length; i++) {
|
||||||
|
let [key, value] = iterable[i];
|
||||||
|
this.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_hashKey: function(key) {
|
||||||
|
let type = typeof(key);
|
||||||
|
return type + ':' + _hashers[type](key);
|
||||||
|
},
|
||||||
|
|
||||||
|
_internalGet: function(key) {
|
||||||
|
let hash = this._hashKey(key);
|
||||||
|
let node = this._pool[hash];
|
||||||
|
|
||||||
|
if (node && _sameValue(node.key, key))
|
||||||
|
return [true, node.value];
|
||||||
|
else
|
||||||
|
return [false, null];
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(key) {
|
||||||
|
return this._internalGet(key)[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
has: function(key) {
|
||||||
|
return this._internalGet(key)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function(key, value) {
|
||||||
|
let hash = this._hashKey(key);
|
||||||
|
let node = this._pool[hash];
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node.key = key;
|
||||||
|
node.value = value;
|
||||||
|
} else {
|
||||||
|
this._pool[hash] = { key: key, value: value };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: function(key) {
|
||||||
|
let hash = this._hashKey(key);
|
||||||
|
let node = this._pool[hash];
|
||||||
|
|
||||||
|
if (node && _sameValue(node.key, key)) {
|
||||||
|
delete this._pool[hash];
|
||||||
|
return [node.key, node.value];
|
||||||
|
} else {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keys: function() {
|
||||||
|
let pool = this._pool;
|
||||||
|
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||||
|
return pool[hash].key;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
values: function() {
|
||||||
|
let pool = this._pool;
|
||||||
|
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||||
|
return pool[hash].value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
items: function() {
|
||||||
|
let pool = this._pool;
|
||||||
|
return Object.getOwnPropertyNames(pool).map(function(hash) {
|
||||||
|
return [pool[hash].key, pool[hash].value];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
size: function() {
|
||||||
|
return Object.getOwnPropertyNames(this._pool).length;
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user