UNPKG

talisman

Version:

Straightforward fuzzy matching, information retrieval and NLP building blocks for JavaScript.

151 lines (118 loc) 4.06 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: false }); exports.default = createMinHash; exports.binning = binning; var _crc = require('./crc32'); var _crc2 = _interopRequireDefault(_crc); var _random = require('pandemonium/random'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // TODO: cleanup the type specification to be able to return an Int32Array // rather than what I feel seems to be buggy. // TODO: seems to be possible to use some XOR optimization to compute random // hashes faster beyound first one. /** * Constants. */ /** * Talisman hash/minhash * ====================== * * JavaScript implementation of the MinHash signature. * * [Reference]: * https://en.wikipedia.org/wiki/MinHash * * [Article]: * Broder, Andrei Z. (1997), "On the resemblance and containment of documents", * Compression and Complexity of Sequences: Proceedings, Positano, * Amalfitan Coast, Salerno, Italy, June 11-13, 1997. */ var MAX_I32 = Math.pow(2, 32) - 1, NEXT_PRIME = 4294967311; /** * Defaults. */ var DEFAULTS = { hashes: 128, rng: Math.random }; /** * Factory creating the hashing function. * * @param {object} options - Options: * @param {number} hashes - Number of hashes of the produced signature. * @return {function} - The hash function. */ function createMinHash(options) { options = options || {}; var pi = options.hashes || DEFAULTS.hashes, rng = options.rng || DEFAULTS.rng, random = (0, _random.createRandom)(rng); // Picking random coefficient & numbers var A = new Set(), B = new Set(); while (A.size < pi) { A.add(random(0, MAX_I32)); }while (B.size < pi) { B.add(random(0, MAX_I32)); }A = Array.from(A); B = Array.from(B); /** * Function returning the MinHash signature for the given sequence. If the * sequence is a string, tokens will be mapped to char codes while if the * sequence is an array of arbitrary strings, the tokens will be mapped to * CRC32 hashes. * * @param {string|array} sequence - Target sequence. * @return {array} - The MinHash signature. */ return function (sequence) { var tokens = {}, isString = typeof sequence === 'string'; // Keeping track of unique tokens for (var i = 0, l = sequence.length; i < l; i++) { // Using char code if hashing if (isString) tokens[sequence.charCodeAt(i)] = true;else tokens[(0, _crc2.default)(sequence[i]) & 0xffffffff] = true; } // Creating the signature var signature = new Float64Array(pi); for (var _i = 0; _i < pi; _i++) { var min = Infinity; // Iterating over tokens & keeping track of min for (var token in tokens) { var hash = (A[_i] * token + B[_i]) % NEXT_PRIME; if (hash < min) min = hash; } signature[_i] = min; } return signature; }; } function binning(options, items) { var minhash = options.minhash, rows = options.rows; if (typeof minhash !== 'function') throw new Error('talisman/hash/minhash#binning: given minhash is not a function.'); var typicalSignature = minhash(items[0]); if (typicalSignature.length % rows !== 0) throw new Error('talisman/hash/minhash#binning: the size of your minhash signatures should be divisible by rows.'); var bins = new Array(items.length), bands = typicalSignature.length / rows, identifiers = new Map(); var integer = 0; for (var i = 0, l = items.length; i < l; i++) { var item = items[i], signature = minhash(item); for (var j = 0; j < bands; j++) { var key = '' + j + '§'; for (var k = j * rows, m = (j + 1) * rows; k < m; k++) { key += signature[k] + (k < m - 1 ? '$' : ''); }if (!identifiers.has(key)) identifiers.set(key, integer++); bins[i] = bins[i] || []; bins[i].push(identifiers.get(key)); } } return bins; } module.exports = exports['default']; exports['default'].binning = exports.binning;