Created
June 19, 2017 13:30
-
-
Save andrew-medvedev/5ccc0c9165ad14077fd1667bb43fc6a6 to your computer and use it in GitHub Desktop.
Some usefull utils. All are tested and production-ready
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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