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.
 
 
 
 
 
 

383 lines
7.8 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]
const lists = []
while (list.length) {
lists.push(list.splice(0, limit))
}
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
}
/**
* Reverse buffer content (swap endiannes)
*/
static reverseBuffer(buffer) {
if (buffer.length < 1)
return buffer
let j = buffer.length - 1
let tmp = 0
for (let i = 0; i < buffer.length / 2; i++) {
tmp = buffer[i]
buffer[i] = buffer[j]
buffer[j] = tmp
j--
}
return buffer
}
/**
* 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