/**
* @file Helper functions.
* @version March 26, 2017
*
* @author Olivier Pirson --- http://www.opimedia.be/
* @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
*/
/**
* Returns true iff arrays are equal.
*
* @param {Array} arrayA
* @param {Array} arrayB
* @param {function} compareFct: function (a, b) --> number
*
* @returns {Boolean}
*/
function arrayIsEquals(arrayA, arrayB, compareFct=compare) {
assert(arrayA instanceof Array, arrayA);
assert(arrayB instanceof Array, arrayB);
assert(typeof compareFct === "function", compareFct);
if (arrayA.length !== arrayB.length) {
return false;
}
else {
for (let i = 0; i < arrayA.length; ++i) {
if (compareFct(arrayA[i], arrayB[i]) !== 0) {
return false;
}
}
return true;
}
}
/**
* Remove from the array
* the first item equals to value according to compareFct.
*
* Returns true iff one item was removed.
*
* @param {Array} array
* @param value
* @param {function} compareFct: function (a, b) --> number
*
* @returns {Boolean}
*/
function arrayRemoveFirst(array, value, compareFct=compare) {
assert(array instanceof Array, array);
assert(typeof compareFct === "function", compareFct);
for (let i = 0; i < array.length; ++i) {
if (compareFct(value, array[i]) === 0) {
array.splice(i, 1);
return true;
}
}
return false;
}
/**
* Shuffle the array (in place)
* by Knuth algorithm.
*
* @param {Array} array
*/
function arrayShuffle(array) {
"use strict";
assert(array instanceof Array, array);
for (let i = 0; i < array.length; ++i) {
const j = getRandomInteger(0, array.length);
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
/**
* If a < 0 then return -1,
* if a = 0 then return 0,
* if a > 0 then return 1.
*
* @param a object that can be compared with <
* @param b object that can be compared with <
*
* @returns {integer} -1, 0, 1
*/
function compare(a, b) {
"use strict";
return (b < a
? 1
: (a < b
? -1
: 0));
}
/**
* Add the CSS class name to a HTML element (if doesn't exist yet).
*
* @param {HTMLElement} htmlElement
* @param {String} name !== ""
*/
function cssAddClass(htmlElement, name) {
"use strict";
assert(htmlElement instanceof HTMLElement, htmlElement);
assert(typeof name === "string", name);
assert(name !== "", name);
const classes = (typeof htmlElement.className === "string"
? htmlElement.className.split(/\s+/)
: []);
for (let i = 0; i < classes.length; ++i) {
if (classes[i] === name) {
return;
}
}
classes.push(name);
htmlElement.className = classes.join(" ");
}
/**
* Returns true iff the HTML element has the CSS class name.
*
* @param {HTMLElement} htmlElement
* @param {String} name !== ""
*
* @returns {Boolean}
*/
function cssHasClass(htmlElement, name) {
"use strict";
assert(htmlElement instanceof HTMLElement, htmlElement);
assert(typeof name === "string", name);
assert(name !== "", name);
const classes = new Set(typeof htmlElement.className === "string"
? htmlElement.className.split(/\s+/)
: []);
return classes.has(name);
}
/**
* If the HTML element has the CSS class name
* then remove it,
* else add it.
*
* @param {HTMLElement} htmlElement
* @param {String} name !== ""
*/
function cssInvertClass(htmlElement, name) {
"use strict";
assert(htmlElement instanceof HTMLElement, htmlElement);
assert(typeof name === "string", name);
assert(name !== "", name);
cssSetClass(htmlElement, name, !cssHasClass(htmlElement, name));
}
/**
* Remove the CSS class name to a HTML element (if exist).
*
* @param {HTMLElement} htmlElement
* @param {String} name !== ""
*/
function cssRemoveClass(htmlElement, name) {
"use strict";
assert(htmlElement instanceof HTMLElement, htmlElement);
assert(typeof name === "string", name);
assert(name !== "", name);
const classes = (typeof htmlElement.className === "string"
? htmlElement.className.split(/\s+/)
: []);
for (let i = 0; i < classes.length; ++i) {
if (classes[i] === name) {
classes.splice(i, 1);
}
}
htmlElement.className = classes.join(" ");
}
/**
* Add or remove a CSS class to a HTML element.
*
* @param {HTMLElement} htmlElement
* @param {String} name !== ""
* @param {Boolean} add
*/
function cssSetClass(htmlElement, name, add) {
"use strict";
assert(htmlElement instanceof HTMLElement, htmlElement);
assert(typeof name === "string", name);
assert(name !== "", name);
assert(typeof add === "boolean", add);
if (add) {
cssAddClass(htmlElement, name);
}
else {
cssRemoveClass(htmlElement, name);
}
}
/**
* Returns a number between min (included) and max (excluded),
* choiced randomly.
*
* @param {Number} min
* @param {Number} max
*
* @returns {Number}
*/
function getRandom(min, max) {
"use strict";
assert(typeof min === "number", min);
assert(typeof max === "number", max);
assert(min < max, min, max);
return Math.random()*(max - min) + min;
}
/**
* Returns false or true,
* choiced randomly.
*
* @returns {Boolean}
*/
function getRandomBoolean() {
"use strict";
return (getRandomInt(0, 2) !== 0);
}
/**
* Returns a integer between min (included) and max (excluded),
* choiced randomly.
*
* @param {Number} min
* @param {Number} max
*
* @returns {integer}
*/
function getRandomInteger(min, max) {
"use strict";
assert(typeof min === "number", min);
assert(typeof max === "number", max);
assert(min < max, min, max);
return Math.floor(getRandom(min, max));
}
/**
* Returns true iff |x - y| <= max(|x|, |y|)*epsilon.
*
* Cf. "Relative epsilon comparisons" in
* https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
*
* @param {Number} x
* @param {Number} y
* @param {Number} epsilon >= 0
*
* @returns {Boolean}
*/
function isFloatAlmostEqual(x, y, epsilon=0.0001) {
assert(typeof x === "number", x);
assert(typeof y === "number", y);
assert(typeof epsilon === "number", epsilon);
assert(epsilon >= 0, epsilon);
return (Math.abs(x - y) <= Math.max(Math.abs(x), Math.abs(y)) * epsilon);
}
/**
* Returns true iff |x| <= epsilon.
*
* @param {Number} x
* @param {Number} epsilon >= 0
*
* @returns {Boolean}
*/
function isFloat0(x, epsilon=0.000001) {
assert(typeof x === "number", x);
assert(typeof epsilon === "number", epsilon);
assert(epsilon >= 0, epsilon);
return (Math.abs(x) <= epsilon);
}
/**
* Returns true iff |x - y| <= epsilon.
*
* @param {Number} x
* @param {Number} y
* @param {Number} epsilon >= 0
*
* @returns {Boolean}
*/
function isFloatEqual(x, y, epsilon=0.000001) {
assert(typeof x === "number", x);
assert(typeof y === "number", y);
assert(typeof epsilon === "number", epsilon);
assert(epsilon >= 0, epsilon);
return (Math.abs(x - y) <= epsilon);
}
/**
* Returns "s" if n >= 2
* else return "".
*
* @param {Number} n
*
* @returns {String}
*/
function s(n) {
"use strict";
return (n >= 2
? "s"
: "");
}
/**
* Returns a new set union of setA and setB.
*
* @param {Set} setA
* @param {Set} setB
*
* @returns {Set}
*/
function setUnion(setA, setB) {
"use strict";
assert(setA instanceof Set, setA);
assert(setB instanceof Set, setB);
const union = new Set(setA);
for (let item of setB) {
union.add(item);
}
return union;
}
/**
* Returns the insertion point of the value in the array
* to maintain sorted order according to compareFct.
*
* Like
* https://docs.python.org/3/library/bisect.html#bisect.bisect_left
*
* @param {Array} sorted Array according to compareFct
* @param value
* @param {function} compareFct: function (a, b) --> boolean
*
* @returns {integer} 0 <= integer <= array.length
*/
function sortedArrayBisectionLeft(array, value, compareFct=compare) {
assert(array instanceof Array, array);
assert(typeof compareFct === "function", compareFct);
var first = 0;
var afterLast = array.length;
var middle;
while (first < afterLast) {
const middle = Math.floor((first + afterLast)/2);
if (compareFct(array[middle], value) < 0) {
first = middle + 1;
}
else {
afterLast = middle;
}
}
return first;
}
/**
* Returns the insertion point of the value in the array
* to maintain sorted order according to compareFct.
*
* Like
* https://docs.python.org/3/library/bisect.html#bisect.insort_left
*
* @param {Array} sorted Array according to compareFct
* @param value
* @param {function} compareFct: function (a, b) --> number
*/
function sortedArrayInsert(array, value, compareFct=compare) {
assert(array instanceof Array, array);
assert(typeof compareFct === "function", compareFct);
var first = 0;
var afterLast = array.length;
var middle;
while (first < afterLast) {
const middle = Math.floor((first + afterLast)/2);
if (compareFct(array[middle], value) < 0) {
first = middle + 1;
}
else {
afterLast = middle;
}
}
array.splice(first, 0, value);
}
/**
* Returns x in a string
* with left padding to have at least length characters.
*
* @param x
* @param {Number} length > 0
* @param {String} pad not empty
*/
function strPad(x, length, pad=" ") {
assert(typeof length === "number", length);
assert(length > 0, length);
assert(typeof pad === "string", pad);
assert(pad !== "");
x = x.toString();
if (x.length >= length) {
return x;
}
else {
const result = pad.repeat(Math.ceil((length - x.length)/pad.length)) + x;
return result;
}
}