Source: helper.js

  1. /**
  2. * @file Helper functions.
  3. * @version March 26, 2017
  4. *
  5. * @author Olivier Pirson --- http://www.opimedia.be/
  6. * @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
  7. */
  8. /**
  9. * Returns true iff arrays are equal.
  10. *
  11. * @param {Array} arrayA
  12. * @param {Array} arrayB
  13. * @param {function} compareFct: function (a, b) --> number
  14. *
  15. * @returns {Boolean}
  16. */
  17. function arrayIsEquals(arrayA, arrayB, compareFct=compare) {
  18. assert(arrayA instanceof Array, arrayA);
  19. assert(arrayB instanceof Array, arrayB);
  20. assert(typeof compareFct === "function", compareFct);
  21. if (arrayA.length !== arrayB.length) {
  22. return false;
  23. }
  24. else {
  25. for (let i = 0; i < arrayA.length; ++i) {
  26. if (compareFct(arrayA[i], arrayB[i]) !== 0) {
  27. return false;
  28. }
  29. }
  30. return true;
  31. }
  32. }
  33. /**
  34. * Remove from the array
  35. * the first item equals to value according to compareFct.
  36. *
  37. * Returns true iff one item was removed.
  38. *
  39. * @param {Array} array
  40. * @param value
  41. * @param {function} compareFct: function (a, b) --> number
  42. *
  43. * @returns {Boolean}
  44. */
  45. function arrayRemoveFirst(array, value, compareFct=compare) {
  46. assert(array instanceof Array, array);
  47. assert(typeof compareFct === "function", compareFct);
  48. for (let i = 0; i < array.length; ++i) {
  49. if (compareFct(value, array[i]) === 0) {
  50. array.splice(i, 1);
  51. return true;
  52. }
  53. }
  54. return false;
  55. }
  56. /**
  57. * Shuffle the array (in place)
  58. * by Knuth algorithm.
  59. *
  60. * @param {Array} array
  61. */
  62. function arrayShuffle(array) {
  63. "use strict";
  64. assert(array instanceof Array, array);
  65. for (let i = 0; i < array.length; ++i) {
  66. const j = getRandomInteger(0, array.length);
  67. const tmp = array[i];
  68. array[i] = array[j];
  69. array[j] = tmp;
  70. }
  71. }
  72. /**
  73. * If a < 0 then return -1,
  74. * if a = 0 then return 0,
  75. * if a > 0 then return 1.
  76. *
  77. * @param a object that can be compared with <
  78. * @param b object that can be compared with <
  79. *
  80. * @returns {integer} -1, 0, 1
  81. */
  82. function compare(a, b) {
  83. "use strict";
  84. return (b < a
  85. ? 1
  86. : (a < b
  87. ? -1
  88. : 0));
  89. }
  90. /**
  91. * Add the CSS class name to a HTML element (if doesn't exist yet).
  92. *
  93. * @param {HTMLElement} htmlElement
  94. * @param {String} name !== ""
  95. */
  96. function cssAddClass(htmlElement, name) {
  97. "use strict";
  98. assert(htmlElement instanceof HTMLElement, htmlElement);
  99. assert(typeof name === "string", name);
  100. assert(name !== "", name);
  101. const classes = (typeof htmlElement.className === "string"
  102. ? htmlElement.className.split(/\s+/)
  103. : []);
  104. for (let i = 0; i < classes.length; ++i) {
  105. if (classes[i] === name) {
  106. return;
  107. }
  108. }
  109. classes.push(name);
  110. htmlElement.className = classes.join(" ");
  111. }
  112. /**
  113. * Returns true iff the HTML element has the CSS class name.
  114. *
  115. * @param {HTMLElement} htmlElement
  116. * @param {String} name !== ""
  117. *
  118. * @returns {Boolean}
  119. */
  120. function cssHasClass(htmlElement, name) {
  121. "use strict";
  122. assert(htmlElement instanceof HTMLElement, htmlElement);
  123. assert(typeof name === "string", name);
  124. assert(name !== "", name);
  125. const classes = new Set(typeof htmlElement.className === "string"
  126. ? htmlElement.className.split(/\s+/)
  127. : []);
  128. return classes.has(name);
  129. }
  130. /**
  131. * If the HTML element has the CSS class name
  132. * then remove it,
  133. * else add it.
  134. *
  135. * @param {HTMLElement} htmlElement
  136. * @param {String} name !== ""
  137. */
  138. function cssInvertClass(htmlElement, name) {
  139. "use strict";
  140. assert(htmlElement instanceof HTMLElement, htmlElement);
  141. assert(typeof name === "string", name);
  142. assert(name !== "", name);
  143. cssSetClass(htmlElement, name, !cssHasClass(htmlElement, name));
  144. }
  145. /**
  146. * Remove the CSS class name to a HTML element (if exist).
  147. *
  148. * @param {HTMLElement} htmlElement
  149. * @param {String} name !== ""
  150. */
  151. function cssRemoveClass(htmlElement, name) {
  152. "use strict";
  153. assert(htmlElement instanceof HTMLElement, htmlElement);
  154. assert(typeof name === "string", name);
  155. assert(name !== "", name);
  156. const classes = (typeof htmlElement.className === "string"
  157. ? htmlElement.className.split(/\s+/)
  158. : []);
  159. for (let i = 0; i < classes.length; ++i) {
  160. if (classes[i] === name) {
  161. classes.splice(i, 1);
  162. }
  163. }
  164. htmlElement.className = classes.join(" ");
  165. }
  166. /**
  167. * Add or remove a CSS class to a HTML element.
  168. *
  169. * @param {HTMLElement} htmlElement
  170. * @param {String} name !== ""
  171. * @param {Boolean} add
  172. */
  173. function cssSetClass(htmlElement, name, add) {
  174. "use strict";
  175. assert(htmlElement instanceof HTMLElement, htmlElement);
  176. assert(typeof name === "string", name);
  177. assert(name !== "", name);
  178. assert(typeof add === "boolean", add);
  179. if (add) {
  180. cssAddClass(htmlElement, name);
  181. }
  182. else {
  183. cssRemoveClass(htmlElement, name);
  184. }
  185. }
  186. /**
  187. * Returns a number between min (included) and max (excluded),
  188. * choiced randomly.
  189. *
  190. * @param {Number} min
  191. * @param {Number} max
  192. *
  193. * @returns {Number}
  194. */
  195. function getRandom(min, max) {
  196. "use strict";
  197. assert(typeof min === "number", min);
  198. assert(typeof max === "number", max);
  199. assert(min < max, min, max);
  200. return Math.random()*(max - min) + min;
  201. }
  202. /**
  203. * Returns false or true,
  204. * choiced randomly.
  205. *
  206. * @returns {Boolean}
  207. */
  208. function getRandomBoolean() {
  209. "use strict";
  210. return (getRandomInt(0, 2) !== 0);
  211. }
  212. /**
  213. * Returns a integer between min (included) and max (excluded),
  214. * choiced randomly.
  215. *
  216. * @param {Number} min
  217. * @param {Number} max
  218. *
  219. * @returns {integer}
  220. */
  221. function getRandomInteger(min, max) {
  222. "use strict";
  223. assert(typeof min === "number", min);
  224. assert(typeof max === "number", max);
  225. assert(min < max, min, max);
  226. return Math.floor(getRandom(min, max));
  227. }
  228. /**
  229. * Returns true iff |x - y| <= max(|x|, |y|)*epsilon.
  230. *
  231. * Cf. "Relative epsilon comparisons" in
  232. * https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
  233. *
  234. * @param {Number} x
  235. * @param {Number} y
  236. * @param {Number} epsilon >= 0
  237. *
  238. * @returns {Boolean}
  239. */
  240. function isFloatAlmostEqual(x, y, epsilon=0.0001) {
  241. assert(typeof x === "number", x);
  242. assert(typeof y === "number", y);
  243. assert(typeof epsilon === "number", epsilon);
  244. assert(epsilon >= 0, epsilon);
  245. return (Math.abs(x - y) <= Math.max(Math.abs(x), Math.abs(y)) * epsilon);
  246. }
  247. /**
  248. * Returns true iff |x| <= epsilon.
  249. *
  250. * @param {Number} x
  251. * @param {Number} epsilon >= 0
  252. *
  253. * @returns {Boolean}
  254. */
  255. function isFloat0(x, epsilon=0.000001) {
  256. assert(typeof x === "number", x);
  257. assert(typeof epsilon === "number", epsilon);
  258. assert(epsilon >= 0, epsilon);
  259. return (Math.abs(x) <= epsilon);
  260. }
  261. /**
  262. * Returns true iff |x - y| <= epsilon.
  263. *
  264. * @param {Number} x
  265. * @param {Number} y
  266. * @param {Number} epsilon >= 0
  267. *
  268. * @returns {Boolean}
  269. */
  270. function isFloatEqual(x, y, epsilon=0.000001) {
  271. assert(typeof x === "number", x);
  272. assert(typeof y === "number", y);
  273. assert(typeof epsilon === "number", epsilon);
  274. assert(epsilon >= 0, epsilon);
  275. return (Math.abs(x - y) <= epsilon);
  276. }
  277. /**
  278. * Returns "s" if n >= 2
  279. * else return "".
  280. *
  281. * @param {Number} n
  282. *
  283. * @returns {String}
  284. */
  285. function s(n) {
  286. "use strict";
  287. return (n >= 2
  288. ? "s"
  289. : "");
  290. }
  291. /**
  292. * Returns a new set union of setA and setB.
  293. *
  294. * @param {Set} setA
  295. * @param {Set} setB
  296. *
  297. * @returns {Set}
  298. */
  299. function setUnion(setA, setB) {
  300. "use strict";
  301. assert(setA instanceof Set, setA);
  302. assert(setB instanceof Set, setB);
  303. const union = new Set(setA);
  304. for (let item of setB) {
  305. union.add(item);
  306. }
  307. return union;
  308. }
  309. /**
  310. * Returns the insertion point of the value in the array
  311. * to maintain sorted order according to compareFct.
  312. *
  313. * Like
  314. * https://docs.python.org/3/library/bisect.html#bisect.bisect_left
  315. *
  316. * @param {Array} sorted Array according to compareFct
  317. * @param value
  318. * @param {function} compareFct: function (a, b) --> boolean
  319. *
  320. * @returns {integer} 0 <= integer <= array.length
  321. */
  322. function sortedArrayBisectionLeft(array, value, compareFct=compare) {
  323. assert(array instanceof Array, array);
  324. assert(typeof compareFct === "function", compareFct);
  325. var first = 0;
  326. var afterLast = array.length;
  327. var middle;
  328. while (first < afterLast) {
  329. const middle = Math.floor((first + afterLast)/2);
  330. if (compareFct(array[middle], value) < 0) {
  331. first = middle + 1;
  332. }
  333. else {
  334. afterLast = middle;
  335. }
  336. }
  337. return first;
  338. }
  339. /**
  340. * Returns the insertion point of the value in the array
  341. * to maintain sorted order according to compareFct.
  342. *
  343. * Like
  344. * https://docs.python.org/3/library/bisect.html#bisect.insort_left
  345. *
  346. * @param {Array} sorted Array according to compareFct
  347. * @param value
  348. * @param {function} compareFct: function (a, b) --> number
  349. */
  350. function sortedArrayInsert(array, value, compareFct=compare) {
  351. assert(array instanceof Array, array);
  352. assert(typeof compareFct === "function", compareFct);
  353. var first = 0;
  354. var afterLast = array.length;
  355. var middle;
  356. while (first < afterLast) {
  357. const middle = Math.floor((first + afterLast)/2);
  358. if (compareFct(array[middle], value) < 0) {
  359. first = middle + 1;
  360. }
  361. else {
  362. afterLast = middle;
  363. }
  364. }
  365. array.splice(first, 0, value);
  366. }
  367. /**
  368. * Returns x in a string
  369. * with left padding to have at least length characters.
  370. *
  371. * @param x
  372. * @param {Number} length > 0
  373. * @param {String} pad not empty
  374. */
  375. function strPad(x, length, pad=" ") {
  376. assert(typeof length === "number", length);
  377. assert(length > 0, length);
  378. assert(typeof pad === "string", pad);
  379. assert(pad !== "");
  380. x = x.toString();
  381. if (x.length >= length) {
  382. return x;
  383. }
  384. else {
  385. const result = pad.repeat(Math.ceil((length - x.length)/pad.length)) + x;
  386. return result;
  387. }
  388. }