generate-words-freqmap
Version:
Generate frequency map from array of strings.
122 lines (113 loc) • 4.77 kB
JavaScript
class CustomError extends Error {
/**
* Create a `CustomError` (same as `Error` but with an extra property called `library`).
* @param message Argument to be passed to base constructor (`Error()`).
* @param library Name of the library in which error will be thrown.
*/
constructor(message, library) {
super(message);
this.library = library;
}
toString() {
return `Error in \`${this.library}\` library:\n\t${this.message}`;
}
}
// messages aren't inside an object because
// tree-shaking isn't possible when exporting an object
// https://medium.com/@rauschma/note-that-default-exporting-objects-is-usually-an-anti-pattern-if-you-want-to-export-the-cf674423ac38#.nibatprx3
// used at `fullfiller/src/validate`
const notEnoughWordsInFreqMap = (minimum, received) => `Given \`wordsArray\` doesn't have enough words to construct \`freqMap\` containing the minimum quantity of words required. Minimum number of words required: ${minimum}. Number of words received: ${received}.`;
/**
* Every freqMap's word in `wordsToEmphasize` will have their weight multiplied by `emphasizeBy`.
*/
function emphasize(freqMapWordAsKey, wordsToEmphasize, emphasizeBy) {
return wordsToEmphasize.reduce((freqMap, word) => freqMap[word] === undefined ? freqMap : {
...freqMap,
[word]: Math.round(freqMap[word] * emphasizeBy)
}, freqMapWordAsKey);
}
/** **freqMapWeightAsKey** example: `{ 1: ['foo', 'bar'], 3: ['baz'] }` */
function generateFreqMapWeightAsKey(freqMapWordAsKey) {
return Object.keys(freqMapWordAsKey).reduce((freqMap, word) => {
const weight = freqMapWordAsKey[word];
return {
...freqMap,
[weight]: (freqMap[weight] || []).concat(word)
};
}, {});
}
/** **freqMapWordAsKey** example: `{ foo: 1, bar: 3 }` */
function generateFreqMapWordAsKey(wordsArray, caseInsensitive) {
return wordsArray.reduce((freqMap, word) => {
const w = caseInsensitive ? word.toLowerCase() : word;
return {
...freqMap,
// using hasOwnProperty instead of check if undefined, otherwise
// if `word` is a built-in object property (e.g. `constructor`)
// would append 1 to this property (which would results in `NaN`)
// instead of creating a new property
[w]: Object.prototype.hasOwnProperty.call(freqMap, w) ? freqMap[w] + 1 : 1
};
}, {});
}
function getFreqMapWordsQuantity(freqMap) {
const wordsQuantity = Object.values(freqMap).reduce((acc, cur) => acc + cur.length, 0);
return wordsQuantity;
}
/** If required by `options`, reduce the quantity of tiers in `freqMap`. */
function shortenFreqMap(freqMap, tierWeightMin, tierWeightMax, mergePosteriorTiersAt) {
return Object.entries(freqMap).reduce((shortened, entry) => {
const weight = Number(entry[0]);
const tier = entry[1];
// if current weight is less than min or more than max, filter out current tier
if (weight < tierWeightMin || tierWeightMax !== -1 &&
// -1 would indicate that `tierWeightMax` functionality is disabled
weight > tierWeightMax) {
return shortened;
}
// if current weight is more than mergePosteriorTiersAt
// merge current tier into mergePosteriorTiersAt tier
if (mergePosteriorTiersAt !== -1 &&
// -1 would indicate that `mergePosteriorTiersAt` functionality is disabled
weight > mergePosteriorTiersAt) {
return {
...shortened,
[mergePosteriorTiersAt]: (shortened[mergePosteriorTiersAt] || []).concat(freqMap[weight])
};
}
return {
...shortened,
[weight]: tier
};
}, {});
}
const optionsDefault = {
emphasizeBy: 2,
wordsQuantityMin: 0,
tierWeightMin: 1,
tierWeightMax: -1,
mergePosteriorTiersAt: -1,
caseInsensitive: false
};
/**
* Generate `freqMap` from `wordsArray`.
* @param wordsArray
* @param wordsToEmphasize Subset of `wordsArray` to emphasize.
* @param optionsArg Miscellaneous options.
* @throws Error if `freqMap` has less words than required by `options.wordsQuantityMin`.
* @returns freqMap.
*/
function generateFreqMap(wordsArray, wordsToEmphasize, optionsArg) {
const options = {
...optionsDefault,
...optionsArg
};
const freqMapWordAsKey = emphasize(generateFreqMapWordAsKey(wordsArray, options.caseInsensitive), wordsToEmphasize || [], options.emphasizeBy);
const freqMap = shortenFreqMap(generateFreqMapWeightAsKey(freqMapWordAsKey), options.tierWeightMin, options.tierWeightMax, options.mergePosteriorTiersAt);
const freqMapWordsQuantity = getFreqMapWordsQuantity(freqMap);
if (freqMapWordsQuantity < options.wordsQuantityMin) {
throw new CustomError(notEnoughWordsInFreqMap(options.wordsQuantityMin, freqMapWordsQuantity), 'generate-words-freqmap');
}
return freqMap;
}
export { generateFreqMap as default };