@rimbu/hashed
Version:
Immutable HashMap and HashSet implementations for TypeScript
592 lines • 22.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hasher = void 0;
var common_1 = require("@rimbu/common");
var stream_1 = require("@rimbu/stream");
var Hasher;
(function (Hasher) {
var MAX_STEP_BITS = 6;
var STRING_INIT = 21;
var BOOL_TRUE = -37;
var BOOL_FALSE = -73;
var OBJ_INIT = 91;
var UNDEF_VALUE = -31;
var NULL_VALUE = -41;
var _anyFlatHasher = createAnyHasher('FLAT');
var _anyShallowHasher = createAnyHasher('SHALLOW');
var _anyDeepHasher = createAnyHasher('DEEP');
/**
* Returns the default `Hasher` instance used by hashed collections.
* @example
* ```ts
* const h = Hasher.defaultHasher()
* h.hash({ a: 1, b: 2 })
* ```
*/
function defaultHasher() {
return _anyShallowHasher;
}
Hasher.defaultHasher = defaultHasher;
function createStringHasher(maxStepBits) {
var maxSteps = 1 << maxStepBits;
return Object.freeze({
isValid: function (obj) {
return typeof obj === 'string';
},
hash: function (value) {
var length = Math.min(value.length, maxSteps);
var stepSize = Math.max(1, value.length >>> maxStepBits);
var result = STRING_INIT;
// implement times 31 = 32 - 1
for (var i = 0; i < length; i += stepSize) {
result = ((result << 5) - result + value.charCodeAt(i)) | 0;
}
return result;
},
});
}
var _stringHasher = createStringHasher(MAX_STEP_BITS);
/**
* Returns a `Hasher` instance for string values.
* @example
* ```ts
* const h = Hasher.stringHasher()
* h.hash('abc')
* ```
*/
function stringHasher() {
return _stringHasher;
}
Hasher.stringHasher = stringHasher;
var _anyToStringHasher = Object.freeze({
isValid: function (obj) {
return true;
},
hash: function (value) {
return _stringHasher.hash(common_1.Eq.convertAnyToString(value));
},
});
/**
* Returns a `Hasher` instance that hashes the string representation of any value.
* @param maxStepBits - the maximum amount of samples to take from the string
* @example
* ```ts
* const h = Hasher.anyToStringHasher()
* h.hash([1, 3, 'a'])
* ```
*/
function anyToStringHasher(maxStepBits) {
if (undefined === maxStepBits)
return _anyToStringHasher;
return createStringHasher(maxStepBits);
}
Hasher.anyToStringHasher = anyToStringHasher;
var _anyJsonStringHasher = Object.freeze({
isValid: function (obj) {
return true;
},
hash: function (value) {
return _stringHasher.hash(JSON.stringify(value));
},
});
/**
* Returns a `Hasher` instance that hashes any value by hashing the string resulting from
* applying JSON.stringify to the value.
* @example
* ```ts
* const h = Hasher.anyJsonStringHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => false
* ```
*/
function anyJsonStringHasher() {
return _anyJsonStringHasher;
}
Hasher.anyJsonStringHasher = anyJsonStringHasher;
function createStringCaseInsensitiveHasher(maxStepBits) {
var maxSteps = 1 << maxStepBits;
return Object.freeze({
isValid: function (obj) {
return typeof obj === 'string';
},
hash: function (value) {
var length = Math.min(value.length, maxSteps);
var stepSize = Math.max(1, value.length >>> maxStepBits);
var result = STRING_INIT;
// implement times 31 = 32 - 1
for (var i = 0; i < length; i += stepSize) {
var char = value.charAt(i).toUpperCase();
result = ((result << 5) - result + char.charCodeAt(0)) | 0;
}
return result;
},
});
}
var _stringCaseInsensitiveHasher = createStringCaseInsensitiveHasher(MAX_STEP_BITS);
/**
* Returns a `Hasher` instance for case-insensitive string values.
* @example
* ```ts
* const h = Hasher.stringCaseInsensitiveHasher()
* console.log(h.hash('Abc') === h.hash('aBC'))
* // => true
* ```
*/
function stringCaseInsensitiveHasher() {
return _stringCaseInsensitiveHasher;
}
Hasher.stringCaseInsensitiveHasher = stringCaseInsensitiveHasher;
function createArrayHasher(itemHasher, maxStepBits) {
var maxSteps = 1 << maxStepBits;
return Object.freeze({
isValid: function (obj) {
return Array.isArray(obj);
},
hash: function (value) {
var length = Math.min(value.length, maxSteps);
var stepSize = Math.max(1, value.length >>> maxStepBits);
var result = value.length | 0;
// implement times 31 = 32 - 1
for (var i = 0; i < length; i += stepSize) {
result =
((result << 5) - result + (i + 1) * itemHasher.hash(value[i])) | 0;
}
return result;
},
});
}
var _arrayAnyHasher = createArrayHasher(defaultHasher(), MAX_STEP_BITS);
/**
* Returns a `Hasher` that hashes arrays of elements by sampling the array and using
* the given `itemHasher` to hash the sampled elements.
* @typeparam T - the array element type
* @param options - (optional) an object containing the following items:<br/>
* - itemHasher: (optional) a Hasher instance used to hash elements in the array<br/>
* - maxStepBits: (optional) the amount of bits to determine the maximum amount of array
* elements to process
* @example
* ```ts
* const h = Hasher.arrayHasher()
* console.log(h.hash([1, 2, 3] === h.hash([1, 3, 2])))
* // => false
* ```
*/
function arrayHasher(options) {
var _a, _b;
if (undefined === options)
return _arrayAnyHasher;
return createArrayHasher((_a = options.itemHasher) !== null && _a !== void 0 ? _a : anyFlatHasher(), (_b = options.maxStepBits) !== null && _b !== void 0 ? _b : MAX_STEP_BITS);
}
Hasher.arrayHasher = arrayHasher;
function createStreamSourceHasher(itemHasher, maxStepBits) {
if (itemHasher === void 0) { itemHasher = defaultHasher(); }
if (maxStepBits === void 0) { maxStepBits = MAX_STEP_BITS; }
var maxSteps = 1 << maxStepBits;
return Object.freeze({
isValid: function (obj) {
return (typeof obj === 'object' && obj !== null && Symbol.iterator in obj);
},
hash: function (source) {
var iter = stream_1.Stream.from(source)[Symbol.iterator]();
var hashItems = [];
var storeItems = [];
var skipAmount = 1;
var skipCount = 0;
var done = Symbol('done');
var value;
var length = 0;
while (done !== (value = iter.fastNext(done))) {
length++;
skipCount++;
if (skipCount >= skipAmount) {
storeItems.push(value);
skipCount = 0;
}
if (storeItems.length >= maxSteps) {
hashItems = storeItems.slice();
// remove elements from store items
var newLength = maxSteps >>> 1;
for (var index = 0; index < newLength; index += 1) {
storeItems[index] = storeItems[index * 2];
}
storeItems.length = newLength;
skipAmount *= 2;
}
}
if (hashItems.length === 0) {
hashItems = storeItems;
}
var itemHash = createArrayHasher(itemHasher, maxStepBits).hash(hashItems);
// include length in hash
return ((itemHash << 5) - itemHash + length) | 0;
},
});
}
var _streamSourceAnyHasher = createStreamSourceHasher(defaultHasher(), MAX_STEP_BITS);
/**
* Returns a `Hasher` instance that hashes any `StreamSource` limited to a certain amount
* of elements to prevent hanging on infinite streams.
* @typeparam T - the StreamSource element type
* @param options - (optional) an object containing the following items:<br/>
* - itemHasher: (optional) a Hasher instance used to hash elements in the array<br/>
* - maxStepBits: (optional) the amount of bits to determine the maximum amount of array
* elements to process
* @example
* ```ts
* const h = Hasher.streamSourceHasher()
* h.hash(Stream.random())
* // infinite stream but will not hang due to the max step limit
* ```
*/
function streamSourceHasher(options) {
if (undefined === options)
return _streamSourceAnyHasher;
return createStreamSourceHasher(options.itemHasher, options.maxStepBits);
}
Hasher.streamSourceHasher = streamSourceHasher;
// bitwise operators only work on 32-bit integers
var MIN_HASH = -Math.pow(2, 31);
var MAX_HASH = Math.pow(2, 31) - 1;
var _numberHasher = Object.freeze({
isValid: function (obj) {
return typeof obj === 'number';
},
hash: function (value) {
if (Number.isInteger(value)) {
return value | 0;
}
if (Number.isNaN(value)) {
return MAX_HASH - 1;
}
if (value === Number.POSITIVE_INFINITY) {
return MAX_HASH;
}
if (value === Number.NEGATIVE_INFINITY) {
return MIN_HASH;
}
// not sure what else it could be
return _anyToStringHasher.hash(value);
},
});
/**
* Returns a `Hasher` instance that hashes numbers, including 'special' values like `NaN` and infinities.
* @example
* ```ts
* const h = Hasher.numberHasher()
* console.log(h.hash(Number.POSITIVE_INFINITY) === h.hash(Number.NEGATIVE_INFINITY))
* // => false
* console.log(h.hash(Number.NaN) === h.hash(Number.NaN))
* // => true
* ```
*/
function numberHasher() {
return _numberHasher;
}
Hasher.numberHasher = numberHasher;
var _booleanHasher = Object.freeze({
isValid: function (obj) {
return typeof obj === 'boolean';
},
hash: function (value) {
return value ? BOOL_TRUE : BOOL_FALSE;
},
});
/**
* Returns a `Hasher` instance that hashes booleans.
* @example
* ```ts
* const h = Hasher.booleanHasher()
* console.log(h.hash(true) === h.hash(false))
* // => false
* ```
*/
function booleanHasher() {
return _booleanHasher;
}
Hasher.booleanHasher = booleanHasher;
var _bigintHasher = Object.freeze({
isValid: function (obj) {
return typeof obj === 'bigint';
},
hash: _anyToStringHasher.hash,
});
/**
* Returns a `Hasher` instance that hashes bigints.
* @example
* ```ts
* const h = Hasher.bigintHasher()
* console.log(h.hash(BigInt(5)) === h.hash(BigInt(10)))
* // => false
* ```
*/
function bigintHasher() {
return _bigintHasher;
}
Hasher.bigintHasher = bigintHasher;
/**
* Returns a `Hasher` instance that hashes the `.valueOf` value of the given
* object using the given `valueHasher` for instances of the given `cls` class.
* @typeparam T - the input object type
* @typeparam V - the .valueOf property type
* @param cls - the class containing the constructor to check for validity of a given object
* @param valueHasher - the `Hasher` instance to use for the `.valueOf` values
* @example
* ```ts
* const h = Hasher.createValueOfHasher(Date)
* console.log(h.isValid(new Boolean(true)))
* // => false
* const d1 = new Date()
* const d2 = new Date(d1)
* console.log(h.hash(d1) === h.hash(d2))
* // => true
* ```
*/
function createValueOfHasher(cls, valueHasher) {
if (valueHasher === void 0) { valueHasher = anyFlatHasher(); }
return Object.freeze({
isValid: function (obj) {
return obj instanceof cls;
},
hash: function (value) {
return valueHasher.hash(value.valueOf());
},
});
}
Hasher.createValueOfHasher = createValueOfHasher;
var _BooleanHasher = createValueOfHasher(Boolean, _booleanHasher);
var _DateHasher = createValueOfHasher(Date, _numberHasher);
/**
* Returns a `Hasher` instance that hashes `Date` values.
* @example
* ```ts
* const h = Hasher.dateHasher()
* const d1 = new Date()
* const d2 = new Date(d1)
* console.log(h.hash(d1) === h.hash(d2))
* // => true
* ```
*/
function dateHasher() {
return _DateHasher;
}
Hasher.dateHasher = dateHasher;
var _NumberHasher = createValueOfHasher(Number, _numberHasher);
var _StringHasher = createValueOfHasher(String, _stringHasher);
var _wrappedHashers = [
_BooleanHasher,
_DateHasher,
_NumberHasher,
_StringHasher,
];
function tryWrappedHasher(value) {
var i = -1;
var len = _wrappedHashers.length;
while (++i < len) {
var hasher = _wrappedHashers[i];
if (hasher.isValid(value)) {
return hasher.hash(value);
}
}
return undefined;
}
function createObjectHasher(keyHasher, valueHasher) {
return Object.freeze({
isValid: function (obj) {
return typeof obj === 'object';
},
hash: function (value) {
if (value === null)
return NULL_VALUE;
var result = OBJ_INIT;
// implement order independent hash
for (var key in value) {
var keyValue = value[key];
var keyHash = keyHasher.hash(key);
var valueHash = valueHasher.hash(keyValue);
result = result ^ (keyHash + valueHash);
}
return result;
},
});
}
var _objectShallowHasher = createObjectHasher(anyFlatHasher(), anyFlatHasher());
var _objectDeepHasher = createObjectHasher(anyFlatHasher(), anyDeepHasher());
/**
* Returns a `Hasher` instance that hashes objects of key type K and value type V.
* @typeparam K - the key type
* @typeparam V - the value type
* @param options - (optional) an object containing:<br/>
* - keyHasher: (optional) a Hasher instance that is used to hash object keys<br/>
* - valueHasher: (optional) a Hasher instance that is used to hash object values
* @example
* ```ts
* const h = Hasher.objectHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => true
* ```
*/
function objectHasher(options) {
if (undefined === options)
return _objectShallowHasher;
return createObjectHasher(options.keyHasher, options.valueHasher);
}
Hasher.objectHasher = objectHasher;
/**
* Returns a `Hasher` instance that hashes objects of key type K and value type V.
* If a value is an object or array, it will convert those values to a string.
* @typeparam K - the key type
* @typeparam V - the value type
* @example
* ```ts
* const h = Hasher.objectShallowHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => true
* ```
*/
function objectShallowHasher() {
return _objectShallowHasher;
}
Hasher.objectShallowHasher = objectShallowHasher;
/**
* Returns a `Hasher` instance that hashes objects of key type K and value type V.
* If a value is an object or array, it will recursively hash its values.
* @note be careful with circular structures, they can cause an infinite loop
* @typeparam K - the key type
* @typeparam V - the value type
* @example
* ```ts
* const h = Hasher.objectDeepHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => true
* ```
*/
function objectDeepHasher() {
return _objectDeepHasher;
}
Hasher.objectDeepHasher = objectDeepHasher;
function createAnyHasher(mode, maxStepBits) {
if (maxStepBits === void 0) { maxStepBits = MAX_STEP_BITS; }
return Object.freeze({
isValid: function (obj) {
return true;
},
hash: function (value) {
var valueType = typeof value;
switch (valueType) {
case 'undefined':
return UNDEF_VALUE;
case 'bigint':
return _bigintHasher.hash(value);
case 'boolean':
return _booleanHasher.hash(value);
case 'number':
return _numberHasher.hash(value);
case 'string':
return _stringHasher.hash(value);
case 'function':
case 'symbol':
return _anyToStringHasher.hash(value);
case 'object': {
if (null === value)
return NULL_VALUE;
var result = tryWrappedHasher(value);
if (undefined !== result)
return result;
if (mode !== 'FLAT') {
if (Array.isArray(value)) {
if (mode === 'SHALLOW') {
return createArrayHasher(_anyFlatHasher, MAX_STEP_BITS).hash(value);
}
return createArrayHasher(this, maxStepBits).hash(value);
}
if (_streamSourceAnyHasher.isValid(value)) {
if (mode === 'SHALLOW') {
return createStreamSourceHasher(_anyFlatHasher, maxStepBits).hash(value);
}
return createStreamSourceHasher(this, maxStepBits).hash(value);
}
if (_objectShallowHasher.isValid(value)) {
if (mode === 'SHALLOW')
return _objectShallowHasher.hash(value);
return createObjectHasher(_anyFlatHasher, this).hash(value);
}
}
return _anyToStringHasher.hash(value);
}
}
},
});
}
/**
* Returns a `Hasher` instance that hashes any value, but never traverses into an object
* or array to hash its elements. In those cases it will use toString.
* @example
* ```ts
* const h = Hasher.anyFlatHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => false
* ```
*/
function anyFlatHasher() {
return _anyFlatHasher;
}
Hasher.anyFlatHasher = anyFlatHasher;
/**
* Returns a `Hasher` instance that hashes any value, but only traverses into an object
* or array to hash its elements one level deep. After one level, it will use toString.
* @example
* ```ts
* const h = Hasher.anyShallowHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => true
* console.log(h.hash([{ a: 1, b: 2 }]) === h.hash([{ b: 2, a: 1 }]))
* // => false
* ```
*/
function anyShallowHasher() {
return _anyShallowHasher;
}
Hasher.anyShallowHasher = anyShallowHasher;
/**
* Returns a `Hasher` instance that hashes any value, and traverses into an object
* or array to hash its elements.
* @example
* ```ts
* const h = Hasher.anyDeepHasher()
* console.log(h.hash({ a: 1, b: 2 }) === h.hash({ b: 2, a: 1 }))
* // => true
* console.log(h.hash([{ a: 1, b: 2 }]) === h.hash([{ b: 2, a: 1 }]))
* // => true
* ```
*/
function anyDeepHasher() {
return _anyDeepHasher;
}
Hasher.anyDeepHasher = anyDeepHasher;
/**
* Returns a `Hasher` that will return equal hash values for values in a tuple regardless
* of their order, and uses the given `hasher` function to hash the tuple elements.
* @param hasher - the `Hasher` instance to use for tuple elements
* @example
* ```ts
* const h = Hasher.tupleSymmetric()
* console.log(h.hash(['abc', 'def']) === h.hash(['def', 'abc']))
* ```
*/
function tupleSymmetric(hasher) {
if (hasher === void 0) { hasher = anyShallowHasher(); }
return Object.freeze({
isValid: function (obj) {
return (Array.isArray(obj) &&
obj.length === 2 &&
hasher.isValid(obj[0]) &&
hasher.isValid(obj[1]));
},
hash: function (value) {
return (hasher.hash(value[0]) + hasher.hash(value[1])) | 0;
},
});
}
Hasher.tupleSymmetric = tupleSymmetric;
})(Hasher || (exports.Hasher = Hasher = {}));
//# sourceMappingURL=hasher.cjs.map