You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
7.6 KiB
368 lines
7.6 KiB
/*!
|
|
* lib/util.js
|
|
* Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
|
|
*/
|
|
'use strict'
|
|
|
|
/**
|
|
* Class providing utility functions as static methods
|
|
*/
|
|
class Util {
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
constructor() {}
|
|
|
|
/**
|
|
* Topological ordering of DAG
|
|
* https://en.wikipedia.org/wiki/Topological_sorting
|
|
*
|
|
* Kahn's algorithm
|
|
*
|
|
* L ← Empty list that will contain the sorted elements
|
|
* S ← Set of all nodes with no incoming edge
|
|
* while S is non-empty do
|
|
* remove a node n from S
|
|
* add n to tail of L
|
|
* for each node m with an edge e from n to m do
|
|
* remove edge e from the graph
|
|
* if m has no other incoming edges then
|
|
* insert m into S
|
|
*
|
|
* @param {object} parents - map of {[key]: [incoming edge keys]}
|
|
* @param {object} children - a map of {[key]: [outgoing edge keys]}
|
|
* @returns {object}
|
|
* if graph has edges then
|
|
* return error (graph has at least one cycle)
|
|
* else
|
|
* return L (a topologically sorted order)
|
|
*/
|
|
static topologicalOrdering(parents, children) {
|
|
const S = []
|
|
|
|
for (let node in parents) {
|
|
if (parents[node].length == 0) {
|
|
// Node has no parent (incoming edges)
|
|
S.push(node)
|
|
}
|
|
}
|
|
|
|
const L = []
|
|
|
|
while (S.length > 0) {
|
|
const node = S.pop()
|
|
L.push(node)
|
|
|
|
// Loop over nodes that depend on node
|
|
for (let child of children[node]) {
|
|
let i = parents[child].indexOf(node)
|
|
if (i > -1)
|
|
parents[child].splice(i, 1)
|
|
|
|
if (parents[child].length == 0)
|
|
S.push(child)
|
|
}
|
|
}
|
|
return L
|
|
}
|
|
|
|
/**
|
|
* Serialize a series of asynchronous calls to a function
|
|
* over a list of objects
|
|
* ref: http://www.joezimjs.com/javascript/patterns-asynchronous-programming-promises/
|
|
*/
|
|
static seriesCall(list, fn) {
|
|
const results = []
|
|
|
|
return list.reduce((memo, item) => {
|
|
return memo.then(() => {
|
|
return fn(item)
|
|
}).then(result => {
|
|
results.push(result)
|
|
})
|
|
},
|
|
Promise.resolve()
|
|
).then(function() {
|
|
return results
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Delay the call to a function
|
|
*/
|
|
static delay(ms, v) {
|
|
return new Promise(resolve => {
|
|
setTimeout(resolve.bind(null, v), ms)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Splits a list into a list of lists each with maximum length LIMIT
|
|
*/
|
|
static splitList(list, limit) {
|
|
if (list.length <= limit) {
|
|
return [list]
|
|
} else {
|
|
const lists = []
|
|
// How many lists to create?
|
|
const count = Math.ceil(list.length / limit)
|
|
// How many elements per list (max)?
|
|
const els = Math.ceil(list.length / count)
|
|
|
|
for (let i=0; i < count; i++) {
|
|
lists.push(list.slice(i * els, (i+1) * els))
|
|
}
|
|
return lists
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a string is a valid hex value
|
|
*/
|
|
static isHashStr(hash) {
|
|
const hexRegExp = new RegExp(/^[0-9a-f]*$/, 'i')
|
|
return (typeof hash !== "string") ? false : hexRegExp.test(hash)
|
|
}
|
|
|
|
/**
|
|
* Check if a string is a well formed 256 bits hash
|
|
*/
|
|
static is256Hash(hash) {
|
|
return Util.isHashStr(hash) && hash.length == 64
|
|
}
|
|
|
|
/**
|
|
* Sum an array of values
|
|
*/
|
|
static sum(arr) {
|
|
return arr.reduce((memo, val) => { return memo + val }, 0)
|
|
}
|
|
|
|
/**
|
|
* Mean of an array of values
|
|
*/
|
|
static mean(arr) {
|
|
if (arr.length == 0)
|
|
return NaN
|
|
return sum(arr) / arr.length
|
|
}
|
|
|
|
/**
|
|
* Compare 2 values (asc order)
|
|
*/
|
|
static cmpAsc(a, b) {
|
|
return a - b
|
|
}
|
|
|
|
/**
|
|
* Compare 2 values (desc order)
|
|
*/
|
|
static cmpDesc(a,b) {
|
|
return b - a
|
|
}
|
|
|
|
/**
|
|
* Median of an array of values
|
|
*/
|
|
static median(arr, sorted) {
|
|
if (arr.length == 0) return NaN
|
|
if (arr.length == 1) return arr[0]
|
|
|
|
if (!sorted)
|
|
arr.sort(Util.cmpAsc)
|
|
|
|
const midpoint = Math.floor(arr.length / 2)
|
|
|
|
if (arr.length % 2) {
|
|
// Odd-length array
|
|
return arr[midpoint]
|
|
} else {
|
|
// Even-length array
|
|
return (arr[midpoint-1] + arr[midpoint]) / 2.0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Median Absolute Deviation of an array of values
|
|
*/
|
|
static mad(arr, sorted) {
|
|
const med = Util.median(arr, sorted)
|
|
// Deviations from the median
|
|
const dev = []
|
|
for (let val of arr)
|
|
dev.push(Math.abs(val - med))
|
|
return Util.median(dev)
|
|
}
|
|
|
|
/**
|
|
* Quartiles of an array of values
|
|
*/
|
|
static quartiles(arr, sorted) {
|
|
const q = [NaN,NaN,NaN]
|
|
|
|
if (arr.length < 3) return q
|
|
|
|
if (!sorted)
|
|
arr.sort(Util.cmpAsc)
|
|
|
|
// Set median
|
|
q[1] = Util.median(arr, true)
|
|
|
|
const midpoint = Math.floor(arr.length / 2)
|
|
|
|
if (arr.length % 2) {
|
|
// Odd-length array
|
|
const mod4 = arr.length % 4
|
|
const n = Math.floor(arr.length / 4)
|
|
|
|
if (mod4 == 1) {
|
|
q[0] = (arr[n-1] + 3 * arr[n]) / 4
|
|
q[2] = (3 * arr[3*n] + arr[3*n+1]) / 4
|
|
} else if (mod4 == 3) {
|
|
q[0] = (3 * arr[n] + arr[n+1]) / 4
|
|
q[2] = (arr[3*n+1] + 3 * arr[3*n+2]) / 4
|
|
}
|
|
|
|
} else {
|
|
// Even-length array. Slices are already sorted
|
|
q[0] = Util.median(arr.slice(0, midpoint), true)
|
|
q[2] = Util.median(arr.slice(midpoint), true)
|
|
}
|
|
|
|
return q
|
|
}
|
|
|
|
/**
|
|
* Obtain the value of the PCT-th percentile, where PCT on [0,100]
|
|
*/
|
|
static percentile(arr, pct, sorted) {
|
|
if (arr.length < 2) return NaN
|
|
|
|
if (!sorted)
|
|
arr.sort(Util.cmpAsc)
|
|
|
|
const N = arr.length
|
|
const p = pct/100.0
|
|
|
|
let x // target rank
|
|
|
|
if (p <= 1 / (N + 1)) {
|
|
x = 1
|
|
} else if (p < N / (N + 1)) {
|
|
x = p * (N + 1)
|
|
} else {
|
|
x = N
|
|
}
|
|
|
|
// "Floor-x"
|
|
const fx = Math.floor(x) - 1
|
|
|
|
// "Mod-x"
|
|
const mx = x % 1
|
|
|
|
if (fx + 1 >= N) {
|
|
return arr[fx]
|
|
} else {
|
|
// Linear interpolation between two array values
|
|
return arr[fx] + mx * (arr[fx+1] - arr[fx])
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert bytes to Mb
|
|
*/
|
|
static toMb(bytes) {
|
|
return +(bytes / Util.MB).toFixed(0)
|
|
}
|
|
|
|
/**
|
|
* Convert a date to a unix timestamp
|
|
*/
|
|
static unix() {
|
|
return (Date.now() / 1000) | 0
|
|
}
|
|
|
|
/**
|
|
* Convert a value to a padded string (10 chars)
|
|
*/
|
|
static pad10(v) {
|
|
return (v < 10) ? `0${v}` : `${v}`
|
|
}
|
|
|
|
/**
|
|
* Convert a value to a padded string (100 chars)
|
|
*/
|
|
static pad100(v) {
|
|
if (v < 10) return `00${v}`
|
|
if (v < 100) return `0${v}`
|
|
return `${v}`
|
|
}
|
|
|
|
/**
|
|
* Convert a value to a padded string (1000 chars)
|
|
*/
|
|
static pad1000(v) {
|
|
if (v < 10) return `000${v}`
|
|
if (v < 100) return `00${v}`
|
|
if (v < 1000) return `0${v}`
|
|
return `${v}`
|
|
}
|
|
|
|
/**
|
|
* Left pad
|
|
*/
|
|
static leftPad(number, places, fill) {
|
|
number = Math.round(number)
|
|
places = Math.round(places)
|
|
fill = fill || ' '
|
|
|
|
if (number < 0) return number
|
|
|
|
const mag = (number > 0) ? (Math.floor(Math.log10(number)) + 1) : 1
|
|
const parts = []
|
|
|
|
for(let i=0; i < (places - mag); i++) {
|
|
parts.push(fill)
|
|
}
|
|
|
|
parts.push(number)
|
|
return parts.join('')
|
|
}
|
|
|
|
/**
|
|
* Display a time period, in seconds, as DDD:HH:MM:SS[.MS]
|
|
*/
|
|
static timePeriod(period, milliseconds) {
|
|
milliseconds = !!milliseconds
|
|
|
|
const whole = Math.floor(period)
|
|
const ms = 1000*(period - whole)
|
|
const s = whole % 60
|
|
const m = (whole >= 60) ? Math.floor(whole / 60) % 60 : 0
|
|
const h = (whole >= 3600) ? Math.floor(whole / 3600) % 24 : 0
|
|
const d = (whole >= 86400) ? Math.floor(whole / 86400) : 0
|
|
|
|
const parts = [Util.pad10(h), Util.pad10(m), Util.pad10(s)]
|
|
|
|
if (d > 0)
|
|
parts.splice(0, 0, Util.pad100(d))
|
|
|
|
const str = parts.join(':')
|
|
|
|
if (milliseconds) {
|
|
return str + '.' + Util.pad100(ms)
|
|
} else {
|
|
return str
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 1Mb in bytes
|
|
*/
|
|
Util.MB = 1024*1024
|
|
|
|
|
|
module.exports = Util
|
|
|