Skip to content

Instantly share code, notes, and snippets.

@andrew-medvedev
Created June 19, 2017 13:30
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 andrew-medvedev/5ccc0c9165ad14077fd1667bb43fc6a6 to your computer and use it in GitHub Desktop.
Save andrew-medvedev/5ccc0c9165ad14077fd1667bb43fc6a6 to your computer and use it in GitHub Desktop.
Some usefull utils. All are tested and production-ready
/**
* Created by ihatemicro$oft on 02.08.2016.
*/
'use strict';
var _ = require('underscore'),
assert = require('assert'),
stableSort = require('stable'),
deepClone = require('clone');
var originalNow = _.now;
// Checks if Object "obj" has ONLY key equal to "key" input var
_.hasOnlyKey = function(obj, key){
assert(!_.isUndefined(key) && !_.isNull(key), 'Key must be defined');
let objKeys = _.keys(obj);
return (objKeys.length === 1 && objKeys[0] === key);
};
// Checks if Object obj has ONLY keys presented in keysArray array
_.hasOnlyKeys = function(obj, keysArray){
assert(!_.isUndefined(keysArray) && !_.isNull(keysArray), 'Keys array must be defined');
let sortedInputSample = _.stableSortBy(keysArray, e => e),
sortedObjKeys = _.stableSortBy(_.keys(obj), e => e);
return _.isEqual(sortedInputSample, sortedObjKeys);
};
// Checks if array of items "array" has only unique elements. If array empty or null or undefined - it's always false (O(n*n))
_.hasOnlyUniqueElements = function(array){
if(_.isUndefined(array) || _.isNull(array) || !_.isArray(array) || array.length === 0){
return false;
}
for(let i = 0 ; i < array.length ; i++){
for(let j = 0 ; j < array.length ; j++){
if(i !== j){
if(array[i] === array[j]){
return false;
}
}
}
}
return true;
};
// Checks if "array1" contains all elements of "array2". If second array is null or undefined or empty - it's false
_.containsElementsOf = function(array1, array2){
assert(!_.isUndefined(array1) && !_.isNull(array1), 'First array must be defined');
assert(_.isArray(array1), 'First array must be of Array type');
if(_.isUndefined(array2) || _.isNull(array2) || _.isEmpty(array2)){
return false;
}
for(let i = 0 ; i < array2.length ; i++){
let contains = false;
for(let j = 0 ; j < array1.length ; j++){
if(array1[j] === array2[i]){
contains = true;
break;
}
}
if(!contains){
return false;
}
}
return true;
};
// Finds element in array "array" by function "elementCheckFn" and removes it. And returns it
// Element search starts from the end of array
_.findAndRemove = function(array, elementCheckFn){
assert(!_.isUndefined(array) && !_.isNull(array), 'Array must be defined');
assert(_.isArray(array), 'Array must be of Array type');
if(_.isUndefined(elementCheckFn) || _.isNull(elementCheckFn) || !_.isFunction(elementCheckFn)){
return null;
}
for(let i = array.length - 1 ; i >= 0 ; i--){
if(elementCheckFn(array[i])){
return _.first(array.splice(i, 1));
}
}
return null;
};
// Builds an array where numerical elements placed into index equals to themselves. No duplicates.
// Not presented indexes are replaced by nulls
_.buildNaturalArray = function(elements, theMax){
assert(!_.isUndefined(elements) && !_.isNull(elements), 'Elements must be defined');
if(!_.isArray(elements)){
elements = [elements];
}
if(_.isEmpty(elements) && !theMax){
return [];
}
if(!theMax){
theMax = Math.max.apply(null, elements);
}
let out = [], argsHash = _.mapArrayToObjectKeys(elements);
for(let i = 0 ; i < theMax + 1 ; i++){
out.push(argsHash[i] ? i : null);
}
return out;
};
// Creates new object and maps array values as new object keys
_.mapArrayToObjectKeys = function(array){
let out = {};
if(array){
_.each(array, e => out[e] = true);
}
return out;
};
// It just flips a coin
_.coinFlip = function(){
return Math.round(Math.random()) === 1;
};
// Returns random value from array "donorArray" but expect values from array "exceptArray"
_.getRandomOneFromArrayExceptOf = function(donorArray, exceptArray){
assert(!_.isUndefined(donorArray) && !_.isNull(donorArray), 'Donor array must be defined');
assert(_.isArray(donorArray), 'Donor array must be of Array type');
if(!_.isArray(exceptArray)){
exceptArray = [exceptArray];
}
donorArray = _.withoutArray(donorArray, exceptArray);
return donorArray[Math.floor(Math.random() * donorArray.length)];
};
// Returns null if all members of array "array" are nulls. Returns array itself otherwise
_.returnNullIfMembersAreNull = function(array){
assert(!_.isUndefined(array) && !_.isNull(array), 'Array must be defined');
if(_.isArray(array)){
if(_.isEmpty(array)){
return array;
} else {
for(let i = 0 ; i < array.length ; i++){
if(!_.isNull(array[i])){
return array;
}
}
}
return null;
} else {
return array;
}
};
// Same as without function but has 2 input arguments - 2 arrays of values
_.withoutArray = function(donorArray, withoutValuesArray){
var withoutValuesHash = _.mapArrayToObjectKeys(withoutValuesArray);
for(let i = donorArray.length - 1 ; i >= 0 ; i--){
if(withoutValuesHash[donorArray[i]]){
donorArray.splice(i, 1);
}
}
return donorArray;
};
// Same as sortBy function, but using stable sort instead of native js Array.sort()
_.stableSortBy = function(array, iteratee, ascending){
assert(!_.isUndefined(array) && !_.isNull(array), 'Array must be defined');
if(!_.isBoolean(ascending)){
ascending = true;
}
if(_.isUndefined(iteratee) || _.isNull(iteratee) || !_.isFunction(iteratee)){
return array;
}
return stableSort.inplace(array, (a, b) => {
if(ascending){
return iteratee(a) > iteratee(b);
} else {
return iteratee(a) < iteratee(b);
}
});
};
// Same as "stableSortBy" but returning new sorted array leaving original array untouched
_.stableSortByCopy = function(array, iteratee, ascending){
assert(!_.isUndefined(array) && !_.isNull(array), 'Array must be defined');
if(!_.isBoolean(ascending)){
ascending = true;
}
if(_.isUndefined(iteratee) || _.isNull(iteratee) || !_.isFunction(iteratee)){
return array;
}
return stableSort(array, (a, b) => {
if(ascending){
return iteratee(a) > iteratee(b);
} else {
return iteratee(a) < iteratee(b);
}
});
};
// 0.19999999999999998 => 0.2
_.mrProper = function(doubleVal){
assert(_.isNumber(doubleVal), 'doubleVal must be type of Number');
var x1 = doubleVal.toFixed(2).split('.'),
x2 = Math.round(parseFloat(x1[1][0] + '.' + x1[1][1]));
doubleVal = parseFloat(x1[0] + '.' + x2.toString());
return doubleVal;
};
// Just check win percentage
_.checkChance = function(howMuchPercentToWin){
assert(_.isNumber(howMuchPercentToWin), 'howMuchPercentToWin must be type of Number');
var chance = Math.ceil(Math.random() * 100);
return chance >= 1 && chance <= howMuchPercentToWin;
};
// Get an array of "howMuch" unique random integer numbers in range of [from, to]
_.getUniqueRandoms = function(from, to, howMuch){
assert(_.isNumber(from), 'from must be a number');
assert(_.isNumber(to), 'to must be a number');
assert(to > from, 'to must be greater than from');
assert(_.isNumber(howMuch) && howMuch > 0, 'howMuch must be positive non-zero number');
var out = [];
while(out.length < howMuch){
let rand = _.random(from, to);
for(let i = 0 ; i < out.length ; i++){
if(out[i] === rand){
rand = null;
break;
}
}
if(!_.isNull(rand)){
out.push(rand);
}
}
return out;
};
// Same as getUniqueRandoms but should provide sample array of values instead of "from and to" values
_.getUniqueRandomsSample = function(sample, howMuch){
assert(_.isArray(sample), 'sample must be an array');
assert(_.isNumber(howMuch) && howMuch > 0, 'howMuch must be positive non-zero number');
assert(howMuch <= sample.length, 'Cannot take randoms more than sample contains');
var out = [];
while(out.length < howMuch){
let randIndex = _.random(0, sample.length - 1),
rand = sample[randIndex];
for(let i = 0 ; i < out.length ; i++){
if(out[i] === rand){
rand = null;
break;
}
}
if(!_.isNull(rand)){
out.push(rand);
}
}
return out;
};
// Casts whatever to boolean
_.whateverCastToBoolean = function(whatever){
if(_.isUndefined(whatever) || _.isNull(whatever)){
return false;
}
if(_.isBoolean(whatever)){
return whatever;
}
if(_.isString(whatever)){
return !_.isEmpty(whatever) && whatever.toLowerCase() !== 'false';
}
if(_.isNumber(whatever)){
return whatever > 0;
}
return !_.isEmpty(whatever);
};
// Getting property from object by key ignoring case. Undefined if nothing found
_.getPropIgnoreCase = function(obj, keyIgnoreCase, defaultValue){
assert(obj, 'Object must be provided');
assert(_.isObject(obj), 'Object must be a type of object');
assert(keyIgnoreCase, 'Key must be provided');
assert(_.isString(keyIgnoreCase), 'Key must be a type of string');
var _key = keyIgnoreCase.toLowerCase();
for(let prop in obj){
if(obj.hasOwnProperty(prop) && prop.toLowerCase() === _key){
return obj[prop];
}
}
return defaultValue;
};
// It runs all lambdas from array "lambdas" in random sequence
_.shuffleCalls = function(lambdas){
assert(lambdas, 'Lambdas must be provided');
assert(_.isArray(lambdas), 'Lambdas must be an array');
lambdas = _.shuffle(lambdas);
for(let i = 0 ; i < lambdas.length ; i++){
lambdas[i]();
}
};
// Returns random element from collection "collection"
_.randE = function(collection){
assert(collection, 'Collection must be provided');
assert(_.isArray(collection), 'Collection must be an array');
assert(!_.isEmpty(collection), 'Collection must be non-empty');
return collection[_.random(0, collection.length - 1)];
};
// Compares two arrays not paying attention to their order
_.compareArraysWhateverOrder = function(array1, array2){
assert(array1, 'Array 1 must be provided');
assert(_.isArray(array1), 'Array 1 must be an array');
assert(array2, 'Array 2 must be provided');
assert(_.isArray(array2), 'Array 2 must be an array');
if(array1.length !== array2.length){
return false;
} else if(array1.length === 0 && array2.length === 0){
return true;
}
var clone1 = _.clone(array1),
clone2 = _.clone(array2);
while(clone1.length > 0 && clone2.length > 0){
let sample1 = clone1.pop(),
sample2 = null;
if(_.isObject(sample1)){
sample1 = _.clone(sample1);
}
for(let j = 0 ; j < clone2.length ; j++){
sample2 = clone2[j];
if(_.isObject(sample2)){
sample2 = _.clone(sample2);
}
if(_.isEqual(sample1, sample2)){
clone2.splice(j, 1);
break;
} else {
sample2 = null;
}
}
if(_.isNull(sample2)){
return false;
}
}
return (clone1.length === clone2.length);
};
// Deep cloning of an object
_.cloneDeep = function(val){
assert(!_.isUndefined(val), 'Target object must be not undefined');
assert(!_.isNull(val), 'Target object must be not null');
return deepClone(val);
};
// It parses float value and if output is NaN so null returns
_.parseFloatOrNull = function(val){
var out = parseFloat(val);
return isNaN(out) ? null : out;
};
// It parses int value and if output is NaN so null returns
_.parseIntOrNull = function(val){
var out = parseInt(val);
return isNaN(out) ? null : out;
};
// It returns diffs between original and modified objects as arrays of: adding, mods and dels
_.simpleFirstLevelDiff = function(original, modified){
assert(_.isObject(original), 'Original object must be an object');
assert(_.isObject(modified), 'Modified object must be an object');
var a = [], m = [], d = [], out = {};
// find adds and mods
_.each(modified, (v, k) => {
if(_.isUndefined(original[k])){
a.push(k);
} else if(!_.isEqual(original[k], v)){
m.push(k);
}
});
// find dels
_.each(original, (v, k) => {
if(_.isUndefined(modified[k])){
d.push(k);
}
});
if(!_.isEmpty(a)){
out.a = a;
}
if(!_.isEmpty(m)){
out.m = m;
}
if(!_.isEmpty(d)){
out.d = d;
}
return _.keys(out).length > 0 ? out : null;
};
// It calculates end timestamp of some day-period
_.getPeriodEndTimestamp = function(bearingTimestamp, periodDuration, now){
assert(_.isNumber(bearingTimestamp), 'bearingTimestamp must be a number');
assert(_.isNumber(periodDuration), 'periodDuration must be a number');
assert(_.isNumber(now), 'now must be a number');
var msFromMidnightConfig = bearingTimestamp % periodDuration,
nowFromMidnightMs = now % periodDuration,
auctionEndTs = now - nowFromMidnightMs + msFromMidnightConfig;
if(msFromMidnightConfig <= nowFromMidnightMs){
auctionEndTs += periodDuration;
}
return auctionEndTs;
};
// Compare semantic versions. For example: 0.1.0 >= 0.0.9
_.compareVersionsGte = function(version1, version2){
var ver1digits = version1.split('.').map(e => _.parseIntOrNull(e)),
ver2digits = version2.split('.').map(e => _.parseIntOrNull(e));
if(ver1digits.length !== ver2digits.length){
return false;
}
var i = 0;
while(true){
if(ver1digits[i] < ver2digits[i]){
return false;
} else if(ver1digits[i] === ver2digits[i]){
if(++i === ver1digits.length){
return true;
}
} else {
return true;
}
}
};
// Same as _.extend(), but destination object will get only non-null and non-undefined values from second donor-object
_.extendNoNull = function(obj1, obj2){
assert(_.isObject(obj1), 'obj1 must be an object');
assert(_.isObject(obj2), 'obj2 must be an object');
for(let k in obj2){
if(obj2.hasOwnProperty(k) && (_.isNull(obj2[k]) || _.isUndefined(obj2[k]))){
delete obj2[k];
}
}
return _.extend(obj1, obj2);
};
// Overriding underscore's now function
_.now = function() {
return originalNow() + appSettings.constants.globalNowTimeDelta || 0;
};
// Random distribution between receivers
_.randomProportions = function(max, numOfProportions){
assert(_.isNumber(max), 'max must be a number');
assert(max > 0, 'max must be gt 0');
assert(_.isNumber(numOfProportions), 'numOfProportions must be a number');
assert(numOfProportions > 0, 'numOfProportions must be gt 0');
assert(max >= numOfProportions, 'max must be gte numOfProportions');
if(numOfProportions === 1){
return [max];
} else if(max === 2){
return [1, 1];
}
var scopes = _.getUniqueRandoms(1, max - 1, numOfProportions - 1);
_.stableSortBy(scopes, e => e, true);
var proportions = [];
var prev = 0;
for(let i = 0 ; i < scopes.length ; i++){
proportions.push(scopes[i] - prev);
prev = scopes[i];
}
proportions.push(max - prev);
return proportions;
};
// When you need to calculate a sum of progression
_.progressionSum = function(nFrom, nTo, lambda){
if(nTo - nFrom === 1){
return lambda(nFrom);
} else {
let outSum = 0;
_(nTo - nFrom).times(n => outSum += lambda(n + nFrom));
return outSum;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment