Skip to content

Instantly share code, notes, and snippets.

@leidegre
Created August 26, 2020 06:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leidegre/00c9d3ca58c91b253851e5c19c487d58 to your computer and use it in GitHub Desktop.
Save leidegre/00c9d3ca58c91b253851e5c19c487d58 to your computer and use it in GitHub Desktop.
Hashing and strict value equality for JavaScript primitives
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const xxh_1 = require("./xxh");
// Hash randomization
const SEED_ARRAY = (1000000000 * Math.random()) | 0;
const SEED_BIG = (1000000000 * Math.random()) | 0;
const SEED_NUM = (1000000000 * Math.random()) | 0;
const SEED_STR = (1000000000 * Math.random()) | 0;
const SEED_KEY = (1000000000 * Math.random()) | 0;
const SEED_DATE = (1000000000 * Math.random()) | 0;
const SEED_BOOL = (1000000000 * Math.random()) | 0;
const SEED_NULL = (1000000000 * Math.random()) | 0;
const XXH_FALSE = xxh_1.xxh_final(xxh_1.xxh_update(xxh_1.xxh_init(SEED_BOOL), 0));
const XXH_TRUE = xxh_1.xxh_final(xxh_1.xxh_update(xxh_1.xxh_init(SEED_BOOL), 1));
const XXH_NULL = xxh_1.xxh_final(xxh_1.xxh_update(xxh_1.xxh_init(SEED_NULL), 0));
const XXH_PRIV_SYM_NO_DESC = xxh_1.xxh_final(xxh_1.xxh_update(xxh_1.xxh_init(SEED_KEY), 0));
// union
const u = new ArrayBuffer(8);
const i32 = new Int32Array(u);
const i64 = new BigInt64Array(u);
const f64 = new Float64Array(u);
/**
* Compute the hash code of a JavaScript primitive value. Note that objects are not values. Arrays are permitted in the case they are used as tuples.
*
* Improper use will throw `TypeError`.
*
* @param v any JavaScript primitive `bigint`, `boolean`, `number`, `Date`, `Array`, `null`, `string` or `symbol`
*/
function hashCode(v) {
switch (typeof v) {
case "bigint": {
i64[0] = v; // truncate
let h = xxh_1.xxh_init(SEED_BIG);
h = xxh_1.xxh_update(h, i32[0]);
h = xxh_1.xxh_update(h, i32[1]);
return xxh_1.xxh_final(h);
}
case "boolean":
return v ? XXH_TRUE : XXH_FALSE;
case "number": {
f64[0] = v;
let h = xxh_1.xxh_init(SEED_NUM);
h = xxh_1.xxh_update(h, i32[0]);
h = xxh_1.xxh_update(h, i32[1]);
return xxh_1.xxh_final(h);
}
case "object": {
if (v instanceof Date) {
return xxh_1.xxh_final(xxh_1.xxh_update(xxh_1.xxh_init(SEED_DATE), +v));
}
if (Array.isArray(v)) {
let h = xxh_1.xxh_init((SEED_ARRAY + (v.length << 2)) | 0);
for (let i = 0; i < v.length; i++) {
h = xxh_1.xxh_update(h, hashCode(v[i]));
}
return xxh_1.xxh_final(h);
}
if (v === null) {
return XXH_NULL;
}
// the main reason for this is testing hash collisions,
// it's not recommended that you write your own `hashCode()` function
if (_hashCodeFunction(v)) {
return v.hashCode();
}
throw new TypeError(`[object ${v.constructor.name}] is not a supported primitive`);
}
case "string": {
return xxh_1.xxh_str(v, SEED_STR);
}
case "symbol": {
const description = v.description;
if (description !== undefined) {
return xxh_1.xxh_str(description, SEED_KEY);
}
return XXH_PRIV_SYM_NO_DESC;
}
}
throw new TypeError(`value with type ${typeof v} is not a supported primitive`);
}
exports.hashCode = hashCode;
/** test whether object has custom `hashCode()` function */
function _hashCodeFunction(obj) {
return typeof obj.hashCode === "function";
}
function equals(a, b) {
switch (typeof a) {
case "object": {
if (a instanceof Date) {
if (b instanceof Date) {
return +a === +b;
}
return false;
}
if (Array.isArray(a)) {
if (Array.isArray(b)) {
if (a.length === b.length) {
for (let i = 0; i < a.length; i++) {
if (!equals(a[i], b[i])) {
return false;
}
}
return true;
}
}
return false;
}
break;
}
}
return a === b;
}
exports.equals = equals;
//# sourceMappingURL=eq.js.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment