diff --git a/snippets/createDirIfNotExists.md b/snippets/createDirIfNotExists.md
new file mode 100644
index 000000000..d31844ea5
--- /dev/null
+++ b/snippets/createDirIfNotExists.md
@@ -0,0 +1,14 @@
+### createDirIfNotExists
+
+Creates a directory, if it does not exist.
+
+Use `fs.existsSync()` to check if the directory exists, `fs.mkdirSync()` to create it.
+
+```js
+const fs = require('fs');
+const createDirIfNotExists = dir => !fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined;
+```
+
+```js
+createDirIfNotExists('test'); // creates the directory 'test', if it doesn't exist
+```
diff --git a/tag_database b/tag_database
index 45c339bae..be1ed67f5 100644
--- a/tag_database
+++ b/tag_database
@@ -38,6 +38,7 @@ copyToClipboard:browser,string,advanced
countBy:array,object,intermediate
counter:browser,advanced
countOccurrences:array,intermediate
+createDirIfNotExists:node,beginner
createElement:browser,utility,beginner
createEventHub:browser,event,advanced
CSVToArray:string,array,utility,intermediate
diff --git a/test/_30s.js b/test/_30s.js
index 827bb4978..9c2824959 100644
--- a/test/_30s.js
+++ b/test/_30s.js
@@ -1,1509 +1,1511 @@
const fs = typeof require !== "undefined" && require('fs');
const crypto = typeof require !== "undefined" && require('crypto');
-const CSVToArray = (data, delimiter = ',', omitFirstRow = false) =>
- data
- .slice(omitFirstRow ? data.indexOf('\n') + 1 : 0)
- .split('\n')
- .map(v => v.split(delimiter));
-const CSVToJSON = (data, delimiter = ',') => {
- const titles = data.slice(0, data.indexOf('\n')).split(delimiter);
- return data
- .slice(data.indexOf('\n') + 1)
- .split('\n')
- .map(v => {
- const values = v.split(delimiter);
- return titles.reduce((obj, title, index) => ((obj[title] = values[index]), obj), {});
- });
-};
-
-const JSONToFile = (obj, filename) =>
- fs.writeFile(`${filename}.json`, JSON.stringify(obj, null, 2));
-const JSONtoCSV = (arr, columns, delimiter = ',') =>
- [
- columns.join(delimiter),
- ...arr.map(obj =>
- columns.reduce(
- (acc, key) => `${acc}${!acc.length ? '' : delimiter}"${!obj[key] ? '' : obj[key]}"`,
- ''
- )
- )
- ].join('\n');
-const RGBToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
-const URLJoin = (...args) =>
- args
- .join('/')
- .replace(/[\/]+/g, '/')
- .replace(/^(.+):\//, '$1://')
- .replace(/^file:/, 'file:/')
- .replace(/\/(\?|&|#[^!])/g, '$1')
- .replace(/\?/g, '&')
- .replace('&', '?');
-const UUIDGeneratorBrowser = () =>
- ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
- (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
- );
-
-const UUIDGeneratorNode = () =>
- ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
- (c ^ (crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16)
- );
-const all = (arr, fn = Boolean) => arr.every(fn);
-const allEqual = arr => arr.every(val => val === arr[0]);
-const any = (arr, fn = Boolean) => arr.some(fn);
-const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 - v2) < epsilon;
-const arrayToCSV = (arr, delimiter = ',') =>
- arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
-const arrayToHtmlList = (arr, listID) =>
- (el => (
- (el = document.querySelector('#' + listID)),
- (el.innerHTML += arr.map(item => `
${item}`).join(''))
- ))();
-const ary = (fn, n) => (...args) => fn(...args.slice(0, n));
-const atob = str => Buffer.from(str, 'base64').toString('binary');
-const attempt = (fn, ...args) => {
- try {
- return fn(...args);
- } catch (e) {
- return e instanceof Error ? e : new Error(e);
- }
-};
-const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;
-const averageBy = (arr, fn) =>
- arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) /
- arr.length;
-const bifurcate = (arr, filter) =>
- arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []]);
-const bifurcateBy = (arr, fn) =>
- arr.reduce((acc, val, i) => (acc[fn(val, i) ? 0 : 1].push(val), acc), [[], []]);
-const bind = (fn, context, ...boundArgs) => (...args) => fn.apply(context, [...boundArgs, ...args]);
-const bindAll = (obj, ...fns) =>
- fns.forEach(
- fn => (
- (f = obj[fn]),
- (obj[fn] = function() {
- return f.apply(obj);
- })
- )
- );
-const bindKey = (context, fn, ...boundArgs) => (...args) =>
- context[fn].apply(context, [...boundArgs, ...args]);
-const binomialCoefficient = (n, k) => {
- if (Number.isNaN(n) || Number.isNaN(k)) return NaN;
- if (k < 0 || k > n) return 0;
- if (k === 0 || k === n) return 1;
- if (k === 1 || k === n - 1) return n;
- if (n - k < k) k = n - k;
- let res = n;
- for (let j = 2; j <= k; j++) res *= (n - j + 1) / j;
- return Math.round(res);
-};
-const bottomVisible = () =>
- document.documentElement.clientHeight + window.scrollY >=
- (document.documentElement.scrollHeight || document.documentElement.clientHeight);
-const btoa = str => Buffer.from(str, 'binary').toString('base64');
-const byteSize = str => new Blob([str]).size;
+
const all = (arr, fn = Boolean) => arr.every(fn);
+
const allEqual = arr => arr.every(val => val === arr[0]);
+
const any = (arr, fn = Boolean) => arr.some(fn);
+
const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 - v2) < epsilon;
+
const arrayToCSV = (arr, delimiter = ',') =>
+ arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
+
const arrayToHtmlList = (arr, listID) =>
+ (el => (
+ (el = document.querySelector('#' + listID)),
+ (el.innerHTML += arr.map(item => `${item}`).join(''))
+ ))();
+
const ary = (fn, n) => (...args) => fn(...args.slice(0, n));
+
const atob = str => Buffer.from(str, 'base64').toString('binary');
+
const attempt = (fn, ...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return e instanceof Error ? e : new Error(e);
+ }
+};
+
const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;
+
const averageBy = (arr, fn) =>
+ arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) /
+ arr.length;
+
const bifurcate = (arr, filter) =>
+ arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []]);
+
const bifurcateBy = (arr, fn) =>
+ arr.reduce((acc, val, i) => (acc[fn(val, i) ? 0 : 1].push(val), acc), [[], []]);
+
const bind = (fn, context, ...boundArgs) => (...args) => fn.apply(context, [...boundArgs, ...args]);
+
const bindAll = (obj, ...fns) =>
+ fns.forEach(
+ fn => (
+ (f = obj[fn]),
+ (obj[fn] = function() {
+ return f.apply(obj);
+ })
+ )
+ );
+
const bindKey = (context, fn, ...boundArgs) => (...args) =>
+ context[fn].apply(context, [...boundArgs, ...args]);
+
const binomialCoefficient = (n, k) => {
+ if (Number.isNaN(n) || Number.isNaN(k)) return NaN;
+ if (k < 0 || k > n) return 0;
+ if (k === 0 || k === n) return 1;
+ if (k === 1 || k === n - 1) return n;
+ if (n - k < k) k = n - k;
+ let res = n;
+ for (let j = 2; j <= k; j++) res *= (n - j + 1) / j;
+ return Math.round(res);
+};
+
const bottomVisible = () =>
+ document.documentElement.clientHeight + window.scrollY >=
+ (document.documentElement.scrollHeight || document.documentElement.clientHeight);
+
const btoa = str => Buffer.from(str, 'binary').toString('base64');
+
const byteSize = str => new Blob([str]).size;
const call = (key, ...args) => context => context[key](...args);
-const capitalize = ([first, ...rest], lowerRest = false) =>
- first.toUpperCase() + (lowerRest ? rest.join('').toLowerCase() : rest.join(''));
-const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
-const castArray = val => (Array.isArray(val) ? val : [val]);
-const chainAsync = fns => {
- let curr = 0;
- const next = () => fns[curr++](next);
- next();
-};
-const chunk = (arr, size) =>
- Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
- arr.slice(i * size, i * size + size)
- );
-const clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
-const cloneRegExp = regExp => new RegExp(regExp.source, regExp.flags);
-const coalesce = (...args) => args.find(_ => ![undefined, null].includes(_));
-const coalesceFactory = valid => (...args) => args.find(valid);
+
const capitalize = ([first, ...rest], lowerRest = false) =>
+ first.toUpperCase() + (lowerRest ? rest.join('').toLowerCase() : rest.join(''));
+
const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
+
const castArray = val => (Array.isArray(val) ? val : [val]);
+
const chainAsync = fns => {
+ let curr = 0;
+ const next = () => fns[curr++](next);
+ next();
+};
+
const chunk = (arr, size) =>
+ Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
+ arr.slice(i * size, i * size + size)
+ );
+
const clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
+
const cloneRegExp = regExp => new RegExp(regExp.source, regExp.flags);
+
const coalesce = (...args) => args.find(_ => ![undefined, null].includes(_));
+
const coalesceFactory = valid => (...args) => args.find(valid);
const collectInto = fn => (...args) => fn(args);
-const colorize = (...args) => ({
- black: `\x1b[30m${args.join(' ')}`,
- red: `\x1b[31m${args.join(' ')}`,
- green: `\x1b[32m${args.join(' ')}`,
- yellow: `\x1b[33m${args.join(' ')}`,
- blue: `\x1b[34m${args.join(' ')}`,
- magenta: `\x1b[35m${args.join(' ')}`,
- cyan: `\x1b[36m${args.join(' ')}`,
- white: `\x1b[37m${args.join(' ')}`,
- bgBlack: `\x1b[40m${args.join(' ')}\x1b[0m`,
- bgRed: `\x1b[41m${args.join(' ')}\x1b[0m`,
- bgGreen: `\x1b[42m${args.join(' ')}\x1b[0m`,
- bgYellow: `\x1b[43m${args.join(' ')}\x1b[0m`,
- bgBlue: `\x1b[44m${args.join(' ')}\x1b[0m`,
- bgMagenta: `\x1b[45m${args.join(' ')}\x1b[0m`,
- bgCyan: `\x1b[46m${args.join(' ')}\x1b[0m`,
- bgWhite: `\x1b[47m${args.join(' ')}\x1b[0m`
-});
-const compact = arr => arr.filter(Boolean);
-const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
-const composeRight = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
-const converge = (converger, fns) => (...args) => converger(...fns.map(fn => fn.apply(null, args)));
-const copyToClipboard = str => {
- const el = document.createElement('textarea');
- el.value = str;
- el.setAttribute('readonly', '');
- el.style.position = 'absolute';
- el.style.left = '-9999px';
- document.body.appendChild(el);
- const selected =
- document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
- el.select();
- document.execCommand('copy');
- document.body.removeChild(el);
- if (selected) {
- document.getSelection().removeAllRanges();
- document.getSelection().addRange(selected);
- }
-};
-const countBy = (arr, fn) =>
- arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => {
- acc[val] = (acc[val] || 0) + 1;
- return acc;
- }, {});
-const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
-const counter = (selector, start, end, step = 1, duration = 2000) => {
- let current = start,
- _step = (end - start) * step < 0 ? -step : step,
- timer = setInterval(() => {
- current += _step;
- document.querySelector(selector).innerHTML = current;
- if (current >= end) document.querySelector(selector).innerHTML = end;
- if (current >= end) clearInterval(timer);
- }, Math.abs(Math.floor(duration / (end - start))));
- return timer;
-};
-const createElement = str => {
- const el = document.createElement('div');
- el.innerHTML = str;
- return el.firstElementChild;
-};
-const createEventHub = () => ({
- hub: Object.create(null),
- emit(event, data) {
- (this.hub[event] || []).forEach(handler => handler(data));
- },
- on(event, handler) {
- if (!this.hub[event]) this.hub[event] = [];
- this.hub[event].push(handler);
- },
- off(event, handler) {
- const i = (this.hub[event] || []).findIndex(h => h === handler);
- if (i > -1) this.hub[event].splice(i, 1);
- }
-});
-const currentURL = () => window.location.href;
-const curry = (fn, arity = fn.length, ...args) =>
- arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
-const dayOfYear = date =>
- Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
-const debounce = (fn, ms = 0) => {
- let timeoutId;
- return function(...args) {
- clearTimeout(timeoutId);
- timeoutId = setTimeout(() => fn.apply(this, args), ms);
- };
-};
-const decapitalize = ([first, ...rest], upperRest = false) =>
- first.toLowerCase() + (upperRest ? rest.join('').toUpperCase() : rest.join(''));
-const deepClone = obj => {
- let clone = Object.assign({}, obj);
- Object.keys(clone).forEach(
- key => (clone[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
- );
- return Array.isArray(obj) ? (clone.length = obj.length) && Array.from(clone) : clone;
-};
-const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
-const deepFreeze = obj =>
- Object.keys(obj).forEach(
- prop =>
- !(obj[prop] instanceof Object) || Object.isFrozen(obj[prop]) ? null : deepFreeze(obj[prop])
- ) || Object.freeze(obj);
-
-const deepMapKeys = (obj, f) =>
- Array.isArray(obj)
- ? obj.map(val => deepMapKeys(val, f))
- : typeof obj === 'object'
- ? Object.keys(obj).reduce((acc, current) => {
- const val = obj[current];
- acc[f(current)] =
- val !== null && typeof val === 'object' ? deepMapKeys(val, f) : (acc[f(current)] = val);
- return acc;
- }, {})
- : obj;
-const defaults = (obj, ...defs) => Object.assign({}, obj, ...defs.reverse(), obj);
-const defer = (fn, ...args) => setTimeout(fn, 1, ...args);
-const degreesToRads = deg => (deg * Math.PI) / 180.0;
-const delay = (fn, wait, ...args) => setTimeout(fn, wait, ...args);
-const detectDeviceType = () =>
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
- ? 'Mobile'
- : 'Desktop';
-const difference = (a, b) => {
- const s = new Set(b);
- return a.filter(x => !s.has(x));
-};
-const differenceBy = (a, b, fn) => {
- const s = new Set(b.map(fn));
- return a.filter(x => !s.has(fn(x)));
-};
-const differenceWith = (arr, val, comp) => arr.filter(a => val.findIndex(b => comp(a, b)) === -1);
-
-const dig = (obj, target) =>
- target in obj
- ? obj[target]
- : Object.values(obj).reduce((acc, val) => {
- if (acc !== undefined) return acc;
- if (typeof val === 'object') return dig(val, target);
- }, undefined);
-const digitize = n => [...`${n}`].map(i => parseInt(i));
-const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
-const drop = (arr, n = 1) => arr.slice(n);
-const dropRight = (arr, n = 1) => arr.slice(0, -n);
-const dropRightWhile = (arr, func) => {
- while (arr.length > 0 && !func(arr[arr.length - 1])) arr = arr.slice(0, -1);
- return arr;
-};
-const dropWhile = (arr, func) => {
- while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
- return arr;
-};
-const elementContains = (parent, child) => parent !== child && parent.contains(child);
-const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
- const { top, left, bottom, right } = el.getBoundingClientRect();
- const { innerHeight, innerWidth } = window;
- return partiallyVisible
- ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
- ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
- : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
-};
-const elo = ([...ratings], kFactor = 32, selfRating) => {
- const [a, b] = ratings;
- const expectedScore = (self, opponent) => 1 / (1 + 10 ** ((opponent - self) / 400));
- const newRating = (rating, i) =>
- (selfRating || rating) + kFactor * (i - expectedScore(i ? a : b, i ? b : a));
- if (ratings.length === 2) return [newRating(a, 1), newRating(b, 0)];
-
- for (let i = 0, len = ratings.length; i < len; i++) {
- let j = i;
- while (j < len - 1) {
- j++;
- [ratings[i], ratings[j]] = elo([ratings[i], ratings[j]], kFactor);
- }
- }
- return ratings;
-};
-const equals = (a, b) => {
- if (a === b) return true;
- if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
- if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
- if (a === null || a === undefined || b === null || b === undefined) return false;
- if (a.prototype !== b.prototype) return false;
- let keys = Object.keys(a);
- if (keys.length !== Object.keys(b).length) return false;
- return keys.every(k => equals(a[k], b[k]));
-};
-const escapeHTML = str =>
- str.replace(
- /[&<>'"]/g,
- tag =>
- ({
- '&': '&',
- '<': '<',
- '>': '>',
- "'": ''',
- '"': '"'
- }[tag] || tag)
- );
-const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-const everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1);
-const extendHex = shortHex =>
- '#' +
- shortHex
- .slice(shortHex.startsWith('#') ? 1 : 0)
- .split('')
- .map(x => x + x)
- .join('');
-
-const factorial = n =>
- n < 0
- ? (() => {
- throw new TypeError('Negative numbers are not allowed!');
- })()
- : n <= 1
- ? 1
- : n * factorial(n - 1);
-const fibonacci = n =>
- Array.from({ length: n }).reduce(
- (acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i),
- []
- );
+
const colorize = (...args) => ({
+ black: `\x1b[30m${args.join(' ')}`,
+ red: `\x1b[31m${args.join(' ')}`,
+ green: `\x1b[32m${args.join(' ')}`,
+ yellow: `\x1b[33m${args.join(' ')}`,
+ blue: `\x1b[34m${args.join(' ')}`,
+ magenta: `\x1b[35m${args.join(' ')}`,
+ cyan: `\x1b[36m${args.join(' ')}`,
+ white: `\x1b[37m${args.join(' ')}`,
+ bgBlack: `\x1b[40m${args.join(' ')}\x1b[0m`,
+ bgRed: `\x1b[41m${args.join(' ')}\x1b[0m`,
+ bgGreen: `\x1b[42m${args.join(' ')}\x1b[0m`,
+ bgYellow: `\x1b[43m${args.join(' ')}\x1b[0m`,
+ bgBlue: `\x1b[44m${args.join(' ')}\x1b[0m`,
+ bgMagenta: `\x1b[45m${args.join(' ')}\x1b[0m`,
+ bgCyan: `\x1b[46m${args.join(' ')}\x1b[0m`,
+ bgWhite: `\x1b[47m${args.join(' ')}\x1b[0m`
+});
+
const compact = arr => arr.filter(Boolean);
+
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
+
const composeRight = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
+
const converge = (converger, fns) => (...args) => converger(...fns.map(fn => fn.apply(null, args)));
+
const copyToClipboard = str => {
+ const el = document.createElement('textarea');
+ el.value = str;
+ el.setAttribute('readonly', '');
+ el.style.position = 'absolute';
+ el.style.left = '-9999px';
+ document.body.appendChild(el);
+ const selected =
+ document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
+ el.select();
+ document.execCommand('copy');
+ document.body.removeChild(el);
+ if (selected) {
+ document.getSelection().removeAllRanges();
+ document.getSelection().addRange(selected);
+ }
+};
+
const countBy = (arr, fn) =>
+ arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => {
+ acc[val] = (acc[val] || 0) + 1;
+ return acc;
+ }, {});
+
const counter = (selector, start, end, step = 1, duration = 2000) => {
+ let current = start,
+ _step = (end - start) * step < 0 ? -step : step,
+ timer = setInterval(() => {
+ current += _step;
+ document.querySelector(selector).innerHTML = current;
+ if (current >= end) document.querySelector(selector).innerHTML = end;
+ if (current >= end) clearInterval(timer);
+ }, Math.abs(Math.floor(duration / (end - start))));
+ return timer;
+};
+
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
+
+const createDirIfNotExists = dir => !fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined;
+
const createElement = str => {
+ const el = document.createElement('div');
+ el.innerHTML = str;
+ return el.firstElementChild;
+};
+
const createEventHub = () => ({
+ hub: Object.create(null),
+ emit(event, data) {
+ (this.hub[event] || []).forEach(handler => handler(data));
+ },
+ on(event, handler) {
+ if (!this.hub[event]) this.hub[event] = [];
+ this.hub[event].push(handler);
+ },
+ off(event, handler) {
+ const i = (this.hub[event] || []).findIndex(h => h === handler);
+ if (i > -1) this.hub[event].splice(i, 1);
+ }
+});
+
const CSVToArray = (data, delimiter = ',', omitFirstRow = false) =>
+ data
+ .slice(omitFirstRow ? data.indexOf('\n') + 1 : 0)
+ .split('\n')
+ .map(v => v.split(delimiter));
+
const CSVToJSON = (data, delimiter = ',') => {
+ const titles = data.slice(0, data.indexOf('\n')).split(delimiter);
+ return data
+ .slice(data.indexOf('\n') + 1)
+ .split('\n')
+ .map(v => {
+ const values = v.split(delimiter);
+ return titles.reduce((obj, title, index) => ((obj[title] = values[index]), obj), {});
+ });
+};
+
const currentURL = () => window.location.href;
+
const curry = (fn, arity = fn.length, ...args) =>
+ arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
+
const dayOfYear = date =>
+ Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
+
const debounce = (fn, ms = 0) => {
+ let timeoutId;
+ return function(...args) {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => fn.apply(this, args), ms);
+ };
+};
+
const decapitalize = ([first, ...rest], upperRest = false) =>
+ first.toLowerCase() + (upperRest ? rest.join('').toUpperCase() : rest.join(''));
+
const deepClone = obj => {
+ let clone = Object.assign({}, obj);
+ Object.keys(clone).forEach(
+ key => (clone[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
+ );
+ return Array.isArray(obj) ? (clone.length = obj.length) && Array.from(clone) : clone;
+};
+
const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
+
const deepFreeze = obj =>
+ Object.keys(obj).forEach(
+ prop =>
+ !(obj[prop] instanceof Object) || Object.isFrozen(obj[prop]) ? null : deepFreeze(obj[prop])
+ ) || Object.freeze(obj);
+
+const deepMapKeys = (obj, f) =>
+ Array.isArray(obj)
+ ? obj.map(val => deepMapKeys(val, f))
+ : typeof obj === 'object'
+ ? Object.keys(obj).reduce((acc, current) => {
+ const val = obj[current];
+ acc[f(current)] =
+ val !== null && typeof val === 'object' ? deepMapKeys(val, f) : (acc[f(current)] = val);
+ return acc;
+ }, {})
+ : obj;
+
const defaults = (obj, ...defs) => Object.assign({}, obj, ...defs.reverse(), obj);
+
const defer = (fn, ...args) => setTimeout(fn, 1, ...args);
+
const degreesToRads = deg => (deg * Math.PI) / 180.0;
+
const delay = (fn, wait, ...args) => setTimeout(fn, wait, ...args);
+
const detectDeviceType = () =>
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
+ ? 'Mobile'
+ : 'Desktop';
+
const difference = (a, b) => {
+ const s = new Set(b);
+ return a.filter(x => !s.has(x));
+};
+
const differenceBy = (a, b, fn) => {
+ const s = new Set(b.map(fn));
+ return a.filter(x => !s.has(fn(x)));
+};
+
const differenceWith = (arr, val, comp) => arr.filter(a => val.findIndex(b => comp(a, b)) === -1);
+
+const dig = (obj, target) =>
+ target in obj
+ ? obj[target]
+ : Object.values(obj).reduce((acc, val) => {
+ if (acc !== undefined) return acc;
+ if (typeof val === 'object') return dig(val, target);
+ }, undefined);
+
const digitize = n => [...`${n}`].map(i => parseInt(i));
+
const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
+
const drop = (arr, n = 1) => arr.slice(n);
+
const dropRight = (arr, n = 1) => arr.slice(0, -n);
+
const dropRightWhile = (arr, func) => {
+ while (arr.length > 0 && !func(arr[arr.length - 1])) arr = arr.slice(0, -1);
+ return arr;
+};
+
const dropWhile = (arr, func) => {
+ while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
+ return arr;
+};
+
const elementContains = (parent, child) => parent !== child && parent.contains(child);
+
const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
+ const { top, left, bottom, right } = el.getBoundingClientRect();
+ const { innerHeight, innerWidth } = window;
+ return partiallyVisible
+ ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
+ ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
+ : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
+};
+
const elo = ([...ratings], kFactor = 32, selfRating) => {
+ const [a, b] = ratings;
+ const expectedScore = (self, opponent) => 1 / (1 + 10 ** ((opponent - self) / 400));
+ const newRating = (rating, i) =>
+ (selfRating || rating) + kFactor * (i - expectedScore(i ? a : b, i ? b : a));
+ if (ratings.length === 2) return [newRating(a, 1), newRating(b, 0)];
+
+ for (let i = 0, len = ratings.length; i < len; i++) {
+ let j = i;
+ while (j < len - 1) {
+ j++;
+ [ratings[i], ratings[j]] = elo([ratings[i], ratings[j]], kFactor);
+ }
+ }
+ return ratings;
+};
+
const equals = (a, b) => {
+ if (a === b) return true;
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
+ if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
+ if (a === null || a === undefined || b === null || b === undefined) return false;
+ if (a.prototype !== b.prototype) return false;
+ let keys = Object.keys(a);
+ if (keys.length !== Object.keys(b).length) return false;
+ return keys.every(k => equals(a[k], b[k]));
+};
+
const escapeHTML = str =>
+ str.replace(
+ /[&<>'"]/g,
+ tag =>
+ ({
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''',
+ '"': '"'
+ }[tag] || tag)
+ );
+
const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+
const everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1);
+
const extendHex = shortHex =>
+ '#' +
+ shortHex
+ .slice(shortHex.startsWith('#') ? 1 : 0)
+ .split('')
+ .map(x => x + x)
+ .join('');
+
+const factorial = n =>
+ n < 0
+ ? (() => {
+ throw new TypeError('Negative numbers are not allowed!');
+ })()
+ : n <= 1
+ ? 1
+ : n * factorial(n - 1);
+
const fibonacci = n =>
+ Array.from({ length: n }).reduce(
+ (acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i),
+ []
+ );
const filterFalsy = arr => arr.filter(Boolean);
-const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
-const filterNonUniqueBy = (arr, fn) =>
- arr.filter((v, i) => arr.every((x, j) => (i === j) === fn(v, x, i, j)));
-const findKey = (obj, fn) => Object.keys(obj).find(key => fn(obj[key], key, obj));
-const findLast = (arr, fn) => arr.filter(fn).pop();
-const findLastIndex = (arr, fn) =>
- arr
- .map((val, i) => [i, val])
- .filter(([i, val]) => fn(val, i, arr))
- .pop()[0];
-const findLastKey = (obj, fn) =>
- Object.keys(obj)
- .reverse()
- .find(key => fn(obj[key], key, obj));
-const flatten = (arr, depth = 1) =>
- arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);
-const flattenObject = (obj, prefix = '') =>
- Object.keys(obj).reduce((acc, k) => {
- const pre = prefix.length ? prefix + '.' : '';
- if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
- else acc[pre + k] = obj[k];
- return acc;
- }, {});
+
const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
+
const filterNonUniqueBy = (arr, fn) =>
+ arr.filter((v, i) => arr.every((x, j) => (i === j) === fn(v, x, i, j)));
+
const findKey = (obj, fn) => Object.keys(obj).find(key => fn(obj[key], key, obj));
+
const findLast = (arr, fn) => arr.filter(fn).pop();
+
const findLastIndex = (arr, fn) =>
+ arr
+ .map((val, i) => [i, val])
+ .filter(([i, val]) => fn(val, i, arr))
+ .pop()[0];
+
const findLastKey = (obj, fn) =>
+ Object.keys(obj)
+ .reverse()
+ .find(key => fn(obj[key], key, obj));
+
const flatten = (arr, depth = 1) =>
+ arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);
+
const flattenObject = (obj, prefix = '') =>
+ Object.keys(obj).reduce((acc, k) => {
+ const pre = prefix.length ? prefix + '.' : '';
+ if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
+ else acc[pre + k] = obj[k];
+ return acc;
+ }, {});
const flip = fn => (first, ...rest) => fn(...rest, first);
-const forEachRight = (arr, callback) =>
- arr
- .slice(0)
- .reverse()
- .forEach(callback);
-const forOwn = (obj, fn) => Object.keys(obj).forEach(key => fn(obj[key], key, obj));
-const forOwnRight = (obj, fn) =>
- Object.keys(obj)
- .reverse()
- .forEach(key => fn(obj[key], key, obj));
-const formatDuration = ms => {
- if (ms < 0) ms = -ms;
- const time = {
- day: Math.floor(ms / 86400000),
- hour: Math.floor(ms / 3600000) % 24,
- minute: Math.floor(ms / 60000) % 60,
- second: Math.floor(ms / 1000) % 60,
- millisecond: Math.floor(ms) % 1000
- };
- return Object.entries(time)
- .filter(val => val[1] !== 0)
- .map(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`)
- .join(', ');
-};
-const fromCamelCase = (str, separator = '_') =>
- str
- .replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
- .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
- .toLowerCase();
-const functionName = fn => (console.debug(fn.name), fn);
-const functions = (obj, inherited = false) =>
- (inherited
- ? [...Object.keys(obj), ...Object.keys(Object.getPrototypeOf(obj))]
- : Object.keys(obj)
- ).filter(key => typeof obj[key] === 'function');
-const gcd = (...arr) => {
- const _gcd = (x, y) => (!y ? x : gcd(y, x % y));
- return [...arr].reduce((a, b) => _gcd(a, b));
-};
-const geometricProgression = (end, start = 1, step = 2) =>
- Array.from({ length: Math.floor(Math.log(end / start) / Math.log(step)) + 1 }).map(
- (v, i) => start * step ** i
- );
-const get = (from, ...selectors) =>
- [...selectors].map(s =>
- s
- .replace(/\[([^\[\]]*)\]/g, '.$1.')
- .split('.')
- .filter(t => t !== '')
- .reduce((prev, cur) => prev && prev[cur], from)
- );
-const getColonTimeFromDate = date => date.toTimeString().slice(0, 8);
-const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
- (dateFinal - dateInitial) / (1000 * 3600 * 24);
-const getImages = (el, includeDuplicates = false) => {
- const images = [...el.getElementsByTagName('img')].map(img => img.getAttribute('src'));
- return includeDuplicates ? images : [...new Set(images)];
-};
-const getMeridiemSuffixOfInteger = num =>
- num === 0 || num === 24
- ? 12 + 'am'
- : num === 12
- ? 12 + 'pm'
- : num < 12
- ? (num % 12) + 'am'
- : (num % 12) + 'pm';
-const getScrollPosition = (el = window) => ({
- x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
- y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
-});
-const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
-const getType = v =>
- v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
-const getURLParameters = url =>
- (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce(
- (a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a),
- {}
- );
-const groupBy = (arr, fn) =>
- arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => {
- acc[val] = (acc[val] || []).concat(arr[i]);
- return acc;
- }, {});
-const hammingDistance = (num1, num2) => ((num1 ^ num2).toString(2).match(/1/g) || '').length;
-const hasClass = (el, className) => el.classList.contains(className);
-const hasFlags = (...flags) =>
- flags.every(flag => process.argv.includes(/^-{1,2}/.test(flag) ? flag : '--' + flag));
-const hashBrowser = val =>
- crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(val)).then(h => {
- let hexes = [],
- view = new DataView(h);
- for (let i = 0; i < view.byteLength; i += 4)
- hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8));
- return hexes.join('');
- });
-
-const hashNode = val =>
- new Promise(resolve =>
- setTimeout(
- () =>
- resolve(
- crypto
- .createHash('sha256')
- .update(val)
- .digest('hex')
- ),
- 0
- )
- );
-const head = arr => arr[0];
-const hexToRGB = hex => {
- let alpha = false,
- h = hex.slice(hex.startsWith('#') ? 1 : 0);
- if (h.length === 3) h = [...h].map(x => x + x).join('');
- else if (h.length === 8) alpha = true;
- h = parseInt(h, 16);
- return (
- 'rgb' +
- (alpha ? 'a' : '') +
- '(' +
- (h >>> (alpha ? 24 : 16)) +
- ', ' +
- ((h & (alpha ? 0x00ff0000 : 0x00ff00)) >>> (alpha ? 16 : 8)) +
- ', ' +
- ((h & (alpha ? 0x0000ff00 : 0x0000ff)) >>> (alpha ? 8 : 0)) +
- (alpha ? `, ${h & 0x000000ff}` : '') +
- ')'
- );
-};
-const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
-const httpGet = (url, callback, err = console.error) => {
- const request = new XMLHttpRequest();
- request.open('GET', url, true);
- request.onload = () => callback(request.responseText);
- request.onerror = () => err(request);
- request.send();
-};
-const httpPost = (url, data, callback, err = console.error) => {
- const request = new XMLHttpRequest();
- request.open('POST', url, true);
- request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
- request.onload = () => callback(request.responseText);
- request.onerror = () => err(request);
- request.send(data);
-};
-const httpsRedirect = () => {
- if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
-};
-const hz = (fn, iterations = 100) => {
- const before = performance.now();
- for (let i = 0; i < iterations; i++) fn();
- return (1000 * iterations) / (performance.now() - before);
-};
-const inRange = (n, start, end = null) => {
- if (end && start > end) [end, start] = [start, end];
- return end == null ? n >= 0 && n < start : n >= start && n < end;
-};
-const indentString = (str, count, indent = ' ') => str.replace(/^/gm, indent.repeat(count));
-const indexOfAll = (arr, val) => arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);
-const initial = arr => arr.slice(0, -1);
-const initialize2DArray = (w, h, val = null) =>
- Array.from({ length: h }).map(() => Array.from({ length: w }).fill(val));
-const initializeArrayWithRange = (end, start = 0, step = 1) =>
- Array.from({ length: Math.ceil((end - start + 1) / step) }, (v, i) => i * step + start);
-const initializeArrayWithRangeRight = (end, start = 0, step = 1) =>
- Array.from({ length: Math.ceil((end + 1 - start) / step) }).map(
- (v, i, arr) => (arr.length - i - 1) * step + start
- );
-const initializeArrayWithValues = (n, val = 0) => Array(n).fill(val);
-const initializeNDArray = (val, ...args) =>
- args.length === 0
- ? val
- : Array.from({ length: args[0] }).map(() => initializeNDArray(val, ...args.slice(1)));
-const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
-const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);
-const intersection = (a, b) => {
- const s = new Set(b);
- return a.filter(x => s.has(x));
-};
-const intersectionBy = (a, b, fn) => {
- const s = new Set(b.map(fn));
- return a.filter(x => s.has(fn(x)));
-};
-const intersectionWith = (a, b, comp) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1);
-const invertKeyValues = (obj, fn) =>
- Object.keys(obj).reduce((acc, key) => {
- const val = fn ? fn(obj[key]) : obj[key];
- acc[val] = acc[val] || [];
- acc[val].push(key);
- return acc;
- }, {});
-const is = (type, val) => ![, null].includes(val) && val.constructor === type;
-const isAbsoluteURL = str => /^[a-z][a-z0-9+.-]*:/.test(str);
-const isAfterDate = (dateA, dateB) => dateA > dateB;
-const isAnagram = (str1, str2) => {
- const normalize = str =>
- str
- .toLowerCase()
- .replace(/[^a-z0-9]/gi, '')
- .split('')
- .sort()
- .join('');
- return normalize(str1) === normalize(str2);
-};
-const isArrayLike = obj => obj != null && typeof obj[Symbol.iterator] === 'function';
-const isBeforeDate = (dateA, dateB) => dateA < dateB;
-const isBoolean = val => typeof val === 'boolean';
-const isBrowser = () => ![typeof window, typeof document].includes('undefined');
-const isBrowserTabFocused = () => !document.hidden;
-const isDivisible = (dividend, divisor) => dividend % divisor === 0;
-const isDuplexStream = val =>
- val !== null &&
- typeof val === 'object' &&
- typeof val.pipe === 'function' &&
- typeof val._read === 'function' &&
- typeof val._readableState === 'object' &&
- typeof val._write === 'function' &&
- typeof val._writableState === 'object';
-const isEmpty = val => val == null || !(Object.keys(val) || val).length;
-const isEven = num => num % 2 === 0;
-const isFunction = val => typeof val === 'function';
-const isLowerCase = str => str === str.toLowerCase();
-const isNegativeZero = val => val === 0 && 1 / val === -Infinity;
-const isNil = val => val === undefined || val === null;
-const isNull = val => val === null;
-const isNumber = val => typeof val === 'number';
-const isObject = obj => obj === Object(obj);
-const isObjectLike = val => val !== null && typeof val === 'object';
-const isPlainObject = val => !!val && typeof val === 'object' && val.constructor === Object;
-const isPrime = num => {
- const boundary = Math.floor(Math.sqrt(num));
- for (var i = 2; i <= boundary; i++) if (num % i === 0) return false;
- return num >= 2;
-};
-const isPrimitive = val => Object(val) !== val;
-const isPromiseLike = obj =>
- obj !== null &&
- (typeof obj === 'object' || typeof obj === 'function') &&
- typeof obj.then === 'function';
-const isReadableStream = val =>
- val !== null &&
- typeof val === 'object' &&
- typeof val.pipe === 'function' &&
- typeof val._read === 'function' &&
- typeof val._readableState === 'object';
-const isSameDate = (dateA, dateB) => dateA.toISOString() === dateB.toISOString();
-const isSorted = arr => {
- let direction = -(arr[0] - arr[1]);
- for (let [i, val] of arr.entries()) {
- direction = !direction ? -(arr[i - 1] - arr[i]) : direction;
- if (i === arr.length - 1) return !direction ? 0 : direction;
- else if ((val - arr[i + 1]) * direction > 0) return 0;
- }
-};
-const isStream = val => val !== null && typeof val === 'object' && typeof val.pipe === 'function';
-const isString = val => typeof val === 'string';
-const isSymbol = val => typeof val === 'symbol';
-const isTravisCI = () => 'TRAVIS' in process.env && 'CI' in process.env;
-const isUndefined = val => val === undefined;
-const isUpperCase = str => str === str.toUpperCase();
-const isValidJSON = str => {
- try {
- JSON.parse(str);
- return true;
- } catch (e) {
- return false;
- }
-};
-const isWritableStream = val =>
- val !== null &&
- typeof val === 'object' &&
- typeof val.pipe === 'function' &&
- typeof val._write === 'function' &&
- typeof val._writableState === 'object';
-const join = (arr, separator = ',', end = separator) =>
- arr.reduce(
- (acc, val, i) =>
- i === arr.length - 2
- ? acc + val + end
- : i === arr.length - 1
- ? acc + val
- : acc + val + separator,
- ''
- );
-const last = arr => arr[arr.length - 1];
-const lcm = (...arr) => {
- const gcd = (x, y) => (!y ? x : gcd(y, x % y));
- const _lcm = (x, y) => (x * y) / gcd(x, y);
- return [...arr].reduce((a, b) => _lcm(a, b));
-};
-const longestItem = (...vals) => vals.reduce((a, x) => (x.length > a.length ? x : a));
-const lowercaseKeys = obj =>
- Object.keys(obj).reduce((acc, key) => {
- acc[key.toLowerCase()] = obj[key];
- return acc;
- }, {});
-const luhnCheck = num => {
- let arr = (num + '')
- .split('')
- .reverse()
- .map(x => parseInt(x));
- let lastDigit = arr.splice(0, 1)[0];
- let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9), 0);
- sum += lastDigit;
- return sum % 10 === 0;
-};
-const mapKeys = (obj, fn) =>
- Object.keys(obj).reduce((acc, k) => {
- acc[fn(obj[k], k, obj)] = obj[k];
- return acc;
- }, {});
-const mapObject = (arr, fn) =>
- (a => (
- (a = [arr, arr.map(fn)]), a[0].reduce((acc, val, ind) => ((acc[val] = a[1][ind]), acc), {})
- ))();
-const mapString = (str, fn) =>
- str
- .split('')
- .map((c, i) => fn(c, i, str))
- .join('');
-const mapValues = (obj, fn) =>
- Object.keys(obj).reduce((acc, k) => {
- acc[k] = fn(obj[k], k, obj);
- return acc;
- }, {});
-const mask = (cc, num = 4, mask = '*') => `${cc}`.slice(-num).padStart(`${cc}`.length, mask);
-const matches = (obj, source) =>
- Object.keys(source).every(key => obj.hasOwnProperty(key) && obj[key] === source[key]);
-const matchesWith = (obj, source, fn) =>
- Object.keys(source).every(
- key =>
- obj.hasOwnProperty(key) && fn
- ? fn(obj[key], source[key], key, obj, source)
- : obj[key] == source[key]
- );
-const maxBy = (arr, fn) => Math.max(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
-const maxDate = (...dates) => new Date(Math.max.apply(null, ...dates));
-const maxN = (arr, n = 1) => [...arr].sort((a, b) => b - a).slice(0, n);
-const median = arr => {
- const mid = Math.floor(arr.length / 2),
- nums = [...arr].sort((a, b) => a - b);
- return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
-};
-const memoize = fn => {
- const cache = new Map();
- const cached = function(val) {
- return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
- };
- cached.cache = cache;
- return cached;
-};
-const merge = (...objs) =>
- [...objs].reduce(
- (acc, obj) =>
- Object.keys(obj).reduce((a, k) => {
- acc[k] = acc.hasOwnProperty(k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
- return acc;
- }, {}),
- {}
- );
-const midpoint = ([x1, y1], [x2, y2]) => [(x1 + x2) / 2, (y1 + y2) / 2];
-const minBy = (arr, fn) => Math.min(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
-const minDate = (...dates) => new Date(Math.min.apply(null, ...dates));
-const minN = (arr, n = 1) => [...arr].sort((a, b) => a - b).slice(0, n);
-const mostPerformant = (fns, iterations = 10000) => {
- const times = fns.map(fn => {
- const before = performance.now();
- for (let i = 0; i < iterations; i++) fn();
- return performance.now() - before;
- });
- return times.indexOf(Math.min(...times));
-};
-const negate = func => (...args) => !func(...args);
-const nest = (items, id = null, link = 'parent_id') =>
- items
- .filter(item => item[link] === id)
- .map(item => ({ ...item, children: nest(items, item.id) }));
-const nodeListToArray = nodeList => [...nodeList];
-const none = (arr, fn = Boolean) => !arr.some(fn);
-const nthArg = n => (...args) => args.slice(n)[0];
-const nthElement = (arr, n = 0) => (n === -1 ? arr.slice(n) : arr.slice(n, n + 1))[0];
-const objectFromPairs = arr => arr.reduce((a, [key, val]) => ((a[key] = val), a), {});
-const objectToPairs = obj => Object.keys(obj).map(k => [k, obj[k]]);
-const observeMutations = (element, callback, options) => {
- const observer = new MutationObserver(mutations => mutations.forEach(m => callback(m)));
- observer.observe(
- element,
- Object.assign(
- {
- childList: true,
- attributes: true,
- attributeOldValue: true,
- characterData: true,
- characterDataOldValue: true,
- subtree: true
- },
- options
- )
- );
- return observer;
-};
-const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
-const offset = (arr, offset) => [...arr.slice(offset), ...arr.slice(0, offset)];
-const omit = (obj, arr) =>
- Object.keys(obj)
- .filter(k => !arr.includes(k))
- .reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
-const omitBy = (obj, fn) =>
- Object.keys(obj)
- .filter(k => !fn(obj[k], k))
- .reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
-const on = (el, evt, fn, opts = {}) => {
- const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e);
- el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false);
- if (opts.target) return delegatorFn;
-};
-const onUserInputChange = callback => {
- let type = 'mouse',
- lastTime = 0;
- const mousemoveHandler = () => {
- const now = performance.now();
- if (now - lastTime < 20)
- (type = 'mouse'), callback(type), document.removeEventListener('mousemove', mousemoveHandler);
- lastTime = now;
- };
- document.addEventListener('touchstart', () => {
- if (type === 'touch') return;
- (type = 'touch'), callback(type), document.addEventListener('mousemove', mousemoveHandler);
- });
-};
-const once = fn => {
- let called = false;
- return function(...args) {
- if (called) return;
- called = true;
- return fn.apply(this, args);
- };
-};
-const orderBy = (arr, props, orders) =>
- [...arr].sort((a, b) =>
- props.reduce((acc, prop, i) => {
- if (acc === 0) {
- const [p1, p2] = orders && orders[i] === 'desc' ? [b[prop], a[prop]] : [a[prop], b[prop]];
- acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
- }
- return acc;
- }, 0)
- );
-const over = (...fns) => (...args) => fns.map(fn => fn.apply(null, args));
-const overArgs = (fn, transforms) => (...args) => fn(...args.map((val, i) => transforms[i](val)));
-const pad = (str, length, char = ' ') =>
- str.padStart((str.length + length) / 2, char).padEnd(length, char);
-const palindrome = str => {
- const s = str.toLowerCase().replace(/[\W_]/g, '');
- return s === [...s].reverse().join('');
-};
-const parseCookie = str =>
- str
- .split(';')
- .map(v => v.split('='))
- .reduce((acc, v) => {
- acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
- return acc;
- }, {});
-const partial = (fn, ...partials) => (...args) => fn(...partials, ...args);
-const partialRight = (fn, ...partials) => (...args) => fn(...args, ...partials);
-const partition = (arr, fn) =>
- arr.reduce(
- (acc, val, i, arr) => {
- acc[fn(val, i, arr) ? 0 : 1].push(val);
- return acc;
- },
- [[], []]
- );
-const percentile = (arr, val) =>
- (100 * arr.reduce((acc, v) => acc + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0)) / arr.length;
-const permutations = arr => {
- if (arr.length <= 2) return arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr;
- return arr.reduce(
- (acc, item, i) =>
- acc.concat(
- permutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map(val => [item, ...val])
- ),
- []
- );
-};
-const pick = (obj, arr) =>
- arr.reduce((acc, curr) => (curr in obj && (acc[curr] = obj[curr]), acc), {});
-const pickBy = (obj, fn) =>
- Object.keys(obj)
- .filter(k => fn(obj[k], k))
- .reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
-const pipeAsyncFunctions = (...fns) => arg => fns.reduce((p, f) => p.then(f), Promise.resolve(arg));
-const pipeFunctions = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
-const pluralize = (val, word, plural = word + 's') => {
- const _pluralize = (num, word, plural = word + 's') =>
- [1, -1].includes(Number(num)) ? word : plural;
- if (typeof val === 'object') return (num, word) => _pluralize(num, word, val[word]);
- return _pluralize(val, word, plural);
-};
-const powerset = arr => arr.reduce((a, v) => a.concat(a.map(r => [v].concat(r))), [[]]);
-const prefix = prop => {
- const capitalizedProp = prop.charAt(0).toUpperCase() + prop.slice(1);
- const prefixes = ['', 'webkit', 'moz', 'ms', 'o'];
- const i = prefixes.findIndex(
- prefix => typeof document.body.style[prefix ? prefix + capitalizedProp : prop] !== 'undefined'
- );
- return i !== -1 ? (i === 0 ? prop : prefixes[i] + capitalizedProp) : null;
-};
-const prettyBytes = (num, precision = 3, addSpace = true) => {
- const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0];
- const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1);
- const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision));
- return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent];
-};
-const primes = num => {
- let arr = Array.from({ length: num - 1 }).map((x, i) => i + 2),
- sqroot = Math.floor(Math.sqrt(num)),
- numsTillSqroot = Array.from({ length: sqroot - 1 }).map((x, i) => i + 2);
- numsTillSqroot.forEach(x => (arr = arr.filter(y => y % x !== 0 || y === x)));
- return arr;
-};
-const promisify = func => (...args) =>
- new Promise((resolve, reject) =>
- func(...args, (err, result) => (err ? reject(err) : resolve(result)))
- );
-const pull = (arr, ...args) => {
- let argState = Array.isArray(args[0]) ? args[0] : args;
- let pulled = arr.filter((v, i) => !argState.includes(v));
- arr.length = 0;
- pulled.forEach(v => arr.push(v));
-};
-const pullAtIndex = (arr, pullArr) => {
- let removed = [];
- let pulled = arr
- .map((v, i) => (pullArr.includes(i) ? removed.push(v) : v))
- .filter((v, i) => !pullArr.includes(i));
- arr.length = 0;
- pulled.forEach(v => arr.push(v));
- return removed;
-};
-const pullAtValue = (arr, pullArr) => {
- let removed = [],
- pushToRemove = arr.forEach((v, i) => (pullArr.includes(v) ? removed.push(v) : v)),
- mutateTo = arr.filter((v, i) => !pullArr.includes(v));
- arr.length = 0;
- mutateTo.forEach(v => arr.push(v));
- return removed;
-};
-const pullBy = (arr, ...args) => {
- const length = args.length;
- let fn = length > 1 ? args[length - 1] : undefined;
- fn = typeof fn == 'function' ? (args.pop(), fn) : undefined;
- let argState = (Array.isArray(args[0]) ? args[0] : args).map(val => fn(val));
- let pulled = arr.filter((v, i) => !argState.includes(fn(v)));
- arr.length = 0;
- pulled.forEach(v => arr.push(v));
-};
-const radsToDegrees = rad => (rad * 180.0) / Math.PI;
-const randomHexColorCode = () => {
- let n = (Math.random() * 0xfffff * 1000000).toString(16);
- return '#' + n.slice(0, 6);
-};
-const randomIntArrayInRange = (min, max, n = 1) =>
- Array.from({ length: n }, () => Math.floor(Math.random() * (max - min + 1)) + min);
-const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
-const randomNumberInRange = (min, max) => Math.random() * (max - min) + min;
-
-const readFileLines = filename =>
- fs
- .readFileSync(filename)
- .toString('UTF8')
- .split('\n');
-const rearg = (fn, indexes) => (...args) => fn(...indexes.map(i => args[i]));
-const recordAnimationFrames = (callback, autoStart = true) => {
- let running = true,
- raf;
- const stop = () => {
- running = false;
- cancelAnimationFrame(raf);
- };
- const start = () => {
- running = true;
- run();
- };
- const run = () => {
- raf = requestAnimationFrame(() => {
- callback();
- if (running) run();
- });
- };
- if (autoStart) start();
- return { start, stop };
-};
-const redirect = (url, asLink = true) =>
- asLink ? (window.location.href = url) : window.location.replace(url);
-const reduceSuccessive = (arr, fn, acc) =>
- arr.reduce((res, val, i, arr) => (res.push(fn(res.slice(-1)[0], val, i, arr)), res), [acc]);
-const reduceWhich = (arr, comparator = (a, b) => a - b) =>
- arr.reduce((a, b) => (comparator(a, b) >= 0 ? b : a));
-const reducedFilter = (data, keys, fn) =>
- data.filter(fn).map(el =>
- keys.reduce((acc, key) => {
- acc[key] = el[key];
- return acc;
- }, {})
- );
-const reject = (pred, array) => array.filter((...args) => !pred(...args));
-const remove = (arr, func) =>
- Array.isArray(arr)
- ? arr.filter(func).reduce((acc, val) => {
- arr.splice(arr.indexOf(val), 1);
- return acc.concat(val);
- }, [])
- : [];
-const removeNonASCII = str => str.replace(/[^\x20-\x7E]/g, '');
-const renameKeys = (keysMap, obj) =>
- Object.keys(obj).reduce(
- (acc, key) => ({
- ...acc,
- ...{ [keysMap[key] || key]: obj[key] }
- }),
- {}
- );
-const reverseString = str => [...str].reverse().join('');
-const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
-const runAsync = fn => {
- const worker = new Worker(
- URL.createObjectURL(new Blob([`postMessage((${fn})());`]), {
- type: 'application/javascript; charset=utf-8'
- })
- );
- return new Promise((res, rej) => {
- worker.onmessage = ({ data }) => {
- res(data), worker.terminate();
- };
- worker.onerror = err => {
- rej(err), worker.terminate();
- };
- });
-};
-const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
-const sample = arr => arr[Math.floor(Math.random() * arr.length)];
-const sampleSize = ([...arr], n = 1) => {
- let m = arr.length;
- while (m) {
- const i = Math.floor(Math.random() * m--);
- [arr[m], arr[i]] = [arr[i], arr[m]];
- }
- return arr.slice(0, n);
-};
-const scrollToTop = () => {
- const c = document.documentElement.scrollTop || document.body.scrollTop;
- if (c > 0) {
- window.requestAnimationFrame(scrollToTop);
- window.scrollTo(0, c - c / 8);
- }
-};
-const sdbm = str => {
- let arr = str.split('');
- return arr.reduce(
- (hashCode, currentVal) =>
- (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
- 0
- );
-};
-const serializeCookie = (name, val) => `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
-const setStyle = (el, ruleName, val) => (el.style[ruleName] = val);
-const shallowClone = obj => Object.assign({}, obj);
-const shank = (arr, index = 0, delCount = 0, ...elements) =>
- arr
- .slice(0, index)
- .concat(elements)
- .concat(arr.slice(index + delCount));
-const show = (...el) => [...el].forEach(e => (e.style.display = ''));
-const shuffle = ([...arr]) => {
- let m = arr.length;
- while (m) {
- const i = Math.floor(Math.random() * m--);
- [arr[m], arr[i]] = [arr[i], arr[m]];
- }
- return arr;
-};
-const similarity = (arr, values) => arr.filter(v => values.includes(v));
-const size = val =>
- Array.isArray(val)
- ? val.length
- : val && typeof val === 'object'
- ? val.size || val.length || Object.keys(val).length
- : typeof val === 'string'
- ? new Blob([val]).size
- : 0;
-const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
-const smoothScroll = element =>
- document.querySelector(element).scrollIntoView({
- behavior: 'smooth'
- });
-const sortCharactersInString = str => [...str].sort((a, b) => a.localeCompare(b)).join('');
-const sortedIndex = (arr, n) => {
- const isDescending = arr[0] > arr[arr.length - 1];
- const index = arr.findIndex(el => (isDescending ? n >= el : n <= el));
- return index === -1 ? arr.length : index;
-};
-const sortedIndexBy = (arr, n, fn) => {
- const isDescending = fn(arr[0]) > fn(arr[arr.length - 1]);
- const val = fn(n);
- const index = arr.findIndex(el => (isDescending ? val >= fn(el) : val <= fn(el)));
- return index === -1 ? arr.length : index;
-};
-const sortedLastIndex = (arr, n) => {
- const isDescending = arr[0] > arr[arr.length - 1];
- const index = arr.reverse().findIndex(el => (isDescending ? n <= el : n >= el));
- return index === -1 ? 0 : arr.length - index;
-};
-const sortedLastIndexBy = (arr, n, fn) => {
- const isDescending = fn(arr[0]) > fn(arr[arr.length - 1]);
- const val = fn(n);
- const index = arr
- .map(fn)
- .reverse()
- .findIndex(el => (isDescending ? val <= el : val >= el));
- return index === -1 ? 0 : arr.length - index;
-};
-const splitLines = str => str.split(/\r?\n/);
+
const forEachRight = (arr, callback) =>
+ arr
+ .slice(0)
+ .reverse()
+ .forEach(callback);
+
const formatDuration = ms => {
+ if (ms < 0) ms = -ms;
+ const time = {
+ day: Math.floor(ms / 86400000),
+ hour: Math.floor(ms / 3600000) % 24,
+ minute: Math.floor(ms / 60000) % 60,
+ second: Math.floor(ms / 1000) % 60,
+ millisecond: Math.floor(ms) % 1000
+ };
+ return Object.entries(time)
+ .filter(val => val[1] !== 0)
+ .map(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`)
+ .join(', ');
+};
+
const forOwn = (obj, fn) => Object.keys(obj).forEach(key => fn(obj[key], key, obj));
+
const forOwnRight = (obj, fn) =>
+ Object.keys(obj)
+ .reverse()
+ .forEach(key => fn(obj[key], key, obj));
+
const fromCamelCase = (str, separator = '_') =>
+ str
+ .replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
+ .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
+ .toLowerCase();
+
const functionName = fn => (console.debug(fn.name), fn);
+
const functions = (obj, inherited = false) =>
+ (inherited
+ ? [...Object.keys(obj), ...Object.keys(Object.getPrototypeOf(obj))]
+ : Object.keys(obj)
+ ).filter(key => typeof obj[key] === 'function');
+
const gcd = (...arr) => {
+ const _gcd = (x, y) => (!y ? x : gcd(y, x % y));
+ return [...arr].reduce((a, b) => _gcd(a, b));
+};
+
const geometricProgression = (end, start = 1, step = 2) =>
+ Array.from({ length: Math.floor(Math.log(end / start) / Math.log(step)) + 1 }).map(
+ (v, i) => start * step ** i
+ );
+
const get = (from, ...selectors) =>
+ [...selectors].map(s =>
+ s
+ .replace(/\[([^\[\]]*)\]/g, '.$1.')
+ .split('.')
+ .filter(t => t !== '')
+ .reduce((prev, cur) => prev && prev[cur], from)
+ );
+
const getColonTimeFromDate = date => date.toTimeString().slice(0, 8);
+
const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
+ (dateFinal - dateInitial) / (1000 * 3600 * 24);
+
const getImages = (el, includeDuplicates = false) => {
+ const images = [...el.getElementsByTagName('img')].map(img => img.getAttribute('src'));
+ return includeDuplicates ? images : [...new Set(images)];
+};
+
const getMeridiemSuffixOfInteger = num =>
+ num === 0 || num === 24
+ ? 12 + 'am'
+ : num === 12
+ ? 12 + 'pm'
+ : num < 12
+ ? (num % 12) + 'am'
+ : (num % 12) + 'pm';
+
const getScrollPosition = (el = window) => ({
+ x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
+ y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
+});
+
const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
+
const getType = v =>
+ v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
+
const getURLParameters = url =>
+ (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce(
+ (a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a),
+ {}
+ );
+
const groupBy = (arr, fn) =>
+ arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => {
+ acc[val] = (acc[val] || []).concat(arr[i]);
+ return acc;
+ }, {});
+
const hammingDistance = (num1, num2) => ((num1 ^ num2).toString(2).match(/1/g) || '').length;
+
const hasClass = (el, className) => el.classList.contains(className);
+
const hasFlags = (...flags) =>
+ flags.every(flag => process.argv.includes(/^-{1,2}/.test(flag) ? flag : '--' + flag));
+
const hashBrowser = val =>
+ crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(val)).then(h => {
+ let hexes = [],
+ view = new DataView(h);
+ for (let i = 0; i < view.byteLength; i += 4)
+ hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8));
+ return hexes.join('');
+ });
+
+const hashNode = val =>
+ new Promise(resolve =>
+ setTimeout(
+ () =>
+ resolve(
+ crypto
+ .createHash('sha256')
+ .update(val)
+ .digest('hex')
+ ),
+ 0
+ )
+ );
+
const head = arr => arr[0];
+
const hexToRGB = hex => {
+ let alpha = false,
+ h = hex.slice(hex.startsWith('#') ? 1 : 0);
+ if (h.length === 3) h = [...h].map(x => x + x).join('');
+ else if (h.length === 8) alpha = true;
+ h = parseInt(h, 16);
+ return (
+ 'rgb' +
+ (alpha ? 'a' : '') +
+ '(' +
+ (h >>> (alpha ? 24 : 16)) +
+ ', ' +
+ ((h & (alpha ? 0x00ff0000 : 0x00ff00)) >>> (alpha ? 16 : 8)) +
+ ', ' +
+ ((h & (alpha ? 0x0000ff00 : 0x0000ff)) >>> (alpha ? 8 : 0)) +
+ (alpha ? `, ${h & 0x000000ff}` : '') +
+ ')'
+ );
+};
+
const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
+
const httpGet = (url, callback, err = console.error) => {
+ const request = new XMLHttpRequest();
+ request.open('GET', url, true);
+ request.onload = () => callback(request.responseText);
+ request.onerror = () => err(request);
+ request.send();
+};
+
const httpPost = (url, data, callback, err = console.error) => {
+ const request = new XMLHttpRequest();
+ request.open('POST', url, true);
+ request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
+ request.onload = () => callback(request.responseText);
+ request.onerror = () => err(request);
+ request.send(data);
+};
+
const httpsRedirect = () => {
+ if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
+};
+
const hz = (fn, iterations = 100) => {
+ const before = performance.now();
+ for (let i = 0; i < iterations; i++) fn();
+ return (1000 * iterations) / (performance.now() - before);
+};
+
const indentString = (str, count, indent = ' ') => str.replace(/^/gm, indent.repeat(count));
+
const indexOfAll = (arr, val) => arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);
+
const initial = arr => arr.slice(0, -1);
+
const initialize2DArray = (w, h, val = null) =>
+ Array.from({ length: h }).map(() => Array.from({ length: w }).fill(val));
+
const initializeArrayWithRange = (end, start = 0, step = 1) =>
+ Array.from({ length: Math.ceil((end - start + 1) / step) }, (v, i) => i * step + start);
+
const initializeArrayWithRangeRight = (end, start = 0, step = 1) =>
+ Array.from({ length: Math.ceil((end + 1 - start) / step) }).map(
+ (v, i, arr) => (arr.length - i - 1) * step + start
+ );
+
const initializeArrayWithValues = (n, val = 0) => Array(n).fill(val);
+
const initializeNDArray = (val, ...args) =>
+ args.length === 0
+ ? val
+ : Array.from({ length: args[0] }).map(() => initializeNDArray(val, ...args.slice(1)));
+
const inRange = (n, start, end = null) => {
+ if (end && start > end) [end, start] = [start, end];
+ return end == null ? n >= 0 && n < start : n >= start && n < end;
+};
+
const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
+
const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);
+
const intersection = (a, b) => {
+ const s = new Set(b);
+ return a.filter(x => s.has(x));
+};
+
const intersectionBy = (a, b, fn) => {
+ const s = new Set(b.map(fn));
+ return a.filter(x => s.has(fn(x)));
+};
+
const intersectionWith = (a, b, comp) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1);
+
const invertKeyValues = (obj, fn) =>
+ Object.keys(obj).reduce((acc, key) => {
+ const val = fn ? fn(obj[key]) : obj[key];
+ acc[val] = acc[val] || [];
+ acc[val].push(key);
+ return acc;
+ }, {});
+
const is = (type, val) => ![, null].includes(val) && val.constructor === type;
+
const isAbsoluteURL = str => /^[a-z][a-z0-9+.-]*:/.test(str);
+
const isAfterDate = (dateA, dateB) => dateA > dateB;
+
const isAnagram = (str1, str2) => {
+ const normalize = str =>
+ str
+ .toLowerCase()
+ .replace(/[^a-z0-9]/gi, '')
+ .split('')
+ .sort()
+ .join('');
+ return normalize(str1) === normalize(str2);
+};
+
const isArrayLike = obj => obj != null && typeof obj[Symbol.iterator] === 'function';
+
const isBeforeDate = (dateA, dateB) => dateA < dateB;
+
const isBoolean = val => typeof val === 'boolean';
+
const isBrowser = () => ![typeof window, typeof document].includes('undefined');
+
const isBrowserTabFocused = () => !document.hidden;
+
const isDivisible = (dividend, divisor) => dividend % divisor === 0;
+
const isDuplexStream = val =>
+ val !== null &&
+ typeof val === 'object' &&
+ typeof val.pipe === 'function' &&
+ typeof val._read === 'function' &&
+ typeof val._readableState === 'object' &&
+ typeof val._write === 'function' &&
+ typeof val._writableState === 'object';
+
const isEmpty = val => val == null || !(Object.keys(val) || val).length;
+
const isEven = num => num % 2 === 0;
+
const isFunction = val => typeof val === 'function';
+
const isLowerCase = str => str === str.toLowerCase();
+
const isNegativeZero = val => val === 0 && 1 / val === -Infinity;
+
const isNil = val => val === undefined || val === null;
+
const isNull = val => val === null;
+
const isNumber = val => typeof val === 'number';
+
const isObject = obj => obj === Object(obj);
+
const isObjectLike = val => val !== null && typeof val === 'object';
+
const isPlainObject = val => !!val && typeof val === 'object' && val.constructor === Object;
+
const isPrime = num => {
+ const boundary = Math.floor(Math.sqrt(num));
+ for (var i = 2; i <= boundary; i++) if (num % i === 0) return false;
+ return num >= 2;
+};
+
const isPrimitive = val => Object(val) !== val;
+
const isPromiseLike = obj =>
+ obj !== null &&
+ (typeof obj === 'object' || typeof obj === 'function') &&
+ typeof obj.then === 'function';
+
const isReadableStream = val =>
+ val !== null &&
+ typeof val === 'object' &&
+ typeof val.pipe === 'function' &&
+ typeof val._read === 'function' &&
+ typeof val._readableState === 'object';
+
const isSameDate = (dateA, dateB) => dateA.toISOString() === dateB.toISOString();
+
const isSorted = arr => {
+ let direction = -(arr[0] - arr[1]);
+ for (let [i, val] of arr.entries()) {
+ direction = !direction ? -(arr[i - 1] - arr[i]) : direction;
+ if (i === arr.length - 1) return !direction ? 0 : direction;
+ else if ((val - arr[i + 1]) * direction > 0) return 0;
+ }
+};
+
const isStream = val => val !== null && typeof val === 'object' && typeof val.pipe === 'function';
+
const isString = val => typeof val === 'string';
+
const isSymbol = val => typeof val === 'symbol';
+
const isTravisCI = () => 'TRAVIS' in process.env && 'CI' in process.env;
+
const isUndefined = val => val === undefined;
+
const isUpperCase = str => str === str.toUpperCase();
+
const isValidJSON = str => {
+ try {
+ JSON.parse(str);
+ return true;
+ } catch (e) {
+ return false;
+ }
+};
+
const isWritableStream = val =>
+ val !== null &&
+ typeof val === 'object' &&
+ typeof val.pipe === 'function' &&
+ typeof val._write === 'function' &&
+ typeof val._writableState === 'object';
+
const join = (arr, separator = ',', end = separator) =>
+ arr.reduce(
+ (acc, val, i) =>
+ i === arr.length - 2
+ ? acc + val + end
+ : i === arr.length - 1
+ ? acc + val
+ : acc + val + separator,
+ ''
+ );
+
const JSONtoCSV = (arr, columns, delimiter = ',') =>
+ [
+ columns.join(delimiter),
+ ...arr.map(obj =>
+ columns.reduce(
+ (acc, key) => `${acc}${!acc.length ? '' : delimiter}"${!obj[key] ? '' : obj[key]}"`,
+ ''
+ )
+ )
+ ].join('\n');
+
+const JSONToFile = (obj, filename) =>
+ fs.writeFile(`${filename}.json`, JSON.stringify(obj, null, 2));
+
const last = arr => arr[arr.length - 1];
+
const lcm = (...arr) => {
+ const gcd = (x, y) => (!y ? x : gcd(y, x % y));
+ const _lcm = (x, y) => (x * y) / gcd(x, y);
+ return [...arr].reduce((a, b) => _lcm(a, b));
+};
+
const longestItem = (...vals) => vals.reduce((a, x) => (x.length > a.length ? x : a));
+
const lowercaseKeys = obj =>
+ Object.keys(obj).reduce((acc, key) => {
+ acc[key.toLowerCase()] = obj[key];
+ return acc;
+ }, {});
+
const luhnCheck = num => {
+ let arr = (num + '')
+ .split('')
+ .reverse()
+ .map(x => parseInt(x));
+ let lastDigit = arr.splice(0, 1)[0];
+ let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9), 0);
+ sum += lastDigit;
+ return sum % 10 === 0;
+};
+
const mapKeys = (obj, fn) =>
+ Object.keys(obj).reduce((acc, k) => {
+ acc[fn(obj[k], k, obj)] = obj[k];
+ return acc;
+ }, {});
+
const mapObject = (arr, fn) =>
+ (a => (
+ (a = [arr, arr.map(fn)]), a[0].reduce((acc, val, ind) => ((acc[val] = a[1][ind]), acc), {})
+ ))();
+
const mapString = (str, fn) =>
+ str
+ .split('')
+ .map((c, i) => fn(c, i, str))
+ .join('');
+
const mapValues = (obj, fn) =>
+ Object.keys(obj).reduce((acc, k) => {
+ acc[k] = fn(obj[k], k, obj);
+ return acc;
+ }, {});
+
const mask = (cc, num = 4, mask = '*') => `${cc}`.slice(-num).padStart(`${cc}`.length, mask);
+
const matches = (obj, source) =>
+ Object.keys(source).every(key => obj.hasOwnProperty(key) && obj[key] === source[key]);
+
const matchesWith = (obj, source, fn) =>
+ Object.keys(source).every(
+ key =>
+ obj.hasOwnProperty(key) && fn
+ ? fn(obj[key], source[key], key, obj, source)
+ : obj[key] == source[key]
+ );
+
const maxBy = (arr, fn) => Math.max(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
+
const maxDate = (...dates) => new Date(Math.max.apply(null, ...dates));
+
const maxN = (arr, n = 1) => [...arr].sort((a, b) => b - a).slice(0, n);
+
const median = arr => {
+ const mid = Math.floor(arr.length / 2),
+ nums = [...arr].sort((a, b) => a - b);
+ return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
+};
+
const memoize = fn => {
+ const cache = new Map();
+ const cached = function(val) {
+ return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
+ };
+ cached.cache = cache;
+ return cached;
+};
+
const merge = (...objs) =>
+ [...objs].reduce(
+ (acc, obj) =>
+ Object.keys(obj).reduce((a, k) => {
+ acc[k] = acc.hasOwnProperty(k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
+ return acc;
+ }, {}),
+ {}
+ );
+
const midpoint = ([x1, y1], [x2, y2]) => [(x1 + x2) / 2, (y1 + y2) / 2];
+
const minBy = (arr, fn) => Math.min(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
+
const minDate = (...dates) => new Date(Math.min.apply(null, ...dates));
+
const minN = (arr, n = 1) => [...arr].sort((a, b) => a - b).slice(0, n);
+
const mostPerformant = (fns, iterations = 10000) => {
+ const times = fns.map(fn => {
+ const before = performance.now();
+ for (let i = 0; i < iterations; i++) fn();
+ return performance.now() - before;
+ });
+ return times.indexOf(Math.min(...times));
+};
+
const negate = func => (...args) => !func(...args);
+
const nest = (items, id = null, link = 'parent_id') =>
+ items
+ .filter(item => item[link] === id)
+ .map(item => ({ ...item, children: nest(items, item.id) }));
+
const nodeListToArray = nodeList => [...nodeList];
+
const none = (arr, fn = Boolean) => !arr.some(fn);
+
const nthArg = n => (...args) => args.slice(n)[0];
+
const nthElement = (arr, n = 0) => (n === -1 ? arr.slice(n) : arr.slice(n, n + 1))[0];
+
const objectFromPairs = arr => arr.reduce((a, [key, val]) => ((a[key] = val), a), {});
+
const objectToPairs = obj => Object.keys(obj).map(k => [k, obj[k]]);
+
const observeMutations = (element, callback, options) => {
+ const observer = new MutationObserver(mutations => mutations.forEach(m => callback(m)));
+ observer.observe(
+ element,
+ Object.assign(
+ {
+ childList: true,
+ attributes: true,
+ attributeOldValue: true,
+ characterData: true,
+ characterDataOldValue: true,
+ subtree: true
+ },
+ options
+ )
+ );
+ return observer;
+};
+
const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
+
const offset = (arr, offset) => [...arr.slice(offset), ...arr.slice(0, offset)];
+
const omit = (obj, arr) =>
+ Object.keys(obj)
+ .filter(k => !arr.includes(k))
+ .reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
+
const omitBy = (obj, fn) =>
+ Object.keys(obj)
+ .filter(k => !fn(obj[k], k))
+ .reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
+
const on = (el, evt, fn, opts = {}) => {
+ const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e);
+ el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false);
+ if (opts.target) return delegatorFn;
+};
+
const once = fn => {
+ let called = false;
+ return function(...args) {
+ if (called) return;
+ called = true;
+ return fn.apply(this, args);
+ };
+};
+
const onUserInputChange = callback => {
+ let type = 'mouse',
+ lastTime = 0;
+ const mousemoveHandler = () => {
+ const now = performance.now();
+ if (now - lastTime < 20)
+ (type = 'mouse'), callback(type), document.removeEventListener('mousemove', mousemoveHandler);
+ lastTime = now;
+ };
+ document.addEventListener('touchstart', () => {
+ if (type === 'touch') return;
+ (type = 'touch'), callback(type), document.addEventListener('mousemove', mousemoveHandler);
+ });
+};
+
const orderBy = (arr, props, orders) =>
+ [...arr].sort((a, b) =>
+ props.reduce((acc, prop, i) => {
+ if (acc === 0) {
+ const [p1, p2] = orders && orders[i] === 'desc' ? [b[prop], a[prop]] : [a[prop], b[prop]];
+ acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
+ }
+ return acc;
+ }, 0)
+ );
+
const over = (...fns) => (...args) => fns.map(fn => fn.apply(null, args));
+
const overArgs = (fn, transforms) => (...args) => fn(...args.map((val, i) => transforms[i](val)));
+
const pad = (str, length, char = ' ') =>
+ str.padStart((str.length + length) / 2, char).padEnd(length, char);
+
const palindrome = str => {
+ const s = str.toLowerCase().replace(/[\W_]/g, '');
+ return s === [...s].reverse().join('');
+};
+
const parseCookie = str =>
+ str
+ .split(';')
+ .map(v => v.split('='))
+ .reduce((acc, v) => {
+ acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
+ return acc;
+ }, {});
+
const partial = (fn, ...partials) => (...args) => fn(...partials, ...args);
+
const partialRight = (fn, ...partials) => (...args) => fn(...args, ...partials);
+
const partition = (arr, fn) =>
+ arr.reduce(
+ (acc, val, i, arr) => {
+ acc[fn(val, i, arr) ? 0 : 1].push(val);
+ return acc;
+ },
+ [[], []]
+ );
+
const percentile = (arr, val) =>
+ (100 * arr.reduce((acc, v) => acc + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0)) / arr.length;
+
const permutations = arr => {
+ if (arr.length <= 2) return arr.length === 2 ? [arr, [arr[1], arr[0]]] : arr;
+ return arr.reduce(
+ (acc, item, i) =>
+ acc.concat(
+ permutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map(val => [item, ...val])
+ ),
+ []
+ );
+};
+
const pick = (obj, arr) =>
+ arr.reduce((acc, curr) => (curr in obj && (acc[curr] = obj[curr]), acc), {});
+
const pickBy = (obj, fn) =>
+ Object.keys(obj)
+ .filter(k => fn(obj[k], k))
+ .reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
+
const pipeAsyncFunctions = (...fns) => arg => fns.reduce((p, f) => p.then(f), Promise.resolve(arg));
+
const pipeFunctions = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
+
const pluralize = (val, word, plural = word + 's') => {
+ const _pluralize = (num, word, plural = word + 's') =>
+ [1, -1].includes(Number(num)) ? word : plural;
+ if (typeof val === 'object') return (num, word) => _pluralize(num, word, val[word]);
+ return _pluralize(val, word, plural);
+};
+
const powerset = arr => arr.reduce((a, v) => a.concat(a.map(r => [v].concat(r))), [[]]);
+
const prefix = prop => {
+ const capitalizedProp = prop.charAt(0).toUpperCase() + prop.slice(1);
+ const prefixes = ['', 'webkit', 'moz', 'ms', 'o'];
+ const i = prefixes.findIndex(
+ prefix => typeof document.body.style[prefix ? prefix + capitalizedProp : prop] !== 'undefined'
+ );
+ return i !== -1 ? (i === 0 ? prop : prefixes[i] + capitalizedProp) : null;
+};
+
const prettyBytes = (num, precision = 3, addSpace = true) => {
+ const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+ if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0];
+ const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1);
+ const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision));
+ return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent];
+};
+
const primes = num => {
+ let arr = Array.from({ length: num - 1 }).map((x, i) => i + 2),
+ sqroot = Math.floor(Math.sqrt(num)),
+ numsTillSqroot = Array.from({ length: sqroot - 1 }).map((x, i) => i + 2);
+ numsTillSqroot.forEach(x => (arr = arr.filter(y => y % x !== 0 || y === x)));
+ return arr;
+};
+
const promisify = func => (...args) =>
+ new Promise((resolve, reject) =>
+ func(...args, (err, result) => (err ? reject(err) : resolve(result)))
+ );
+
const pull = (arr, ...args) => {
+ let argState = Array.isArray(args[0]) ? args[0] : args;
+ let pulled = arr.filter((v, i) => !argState.includes(v));
+ arr.length = 0;
+ pulled.forEach(v => arr.push(v));
+};
+
const pullAtIndex = (arr, pullArr) => {
+ let removed = [];
+ let pulled = arr
+ .map((v, i) => (pullArr.includes(i) ? removed.push(v) : v))
+ .filter((v, i) => !pullArr.includes(i));
+ arr.length = 0;
+ pulled.forEach(v => arr.push(v));
+ return removed;
+};
+
const pullAtValue = (arr, pullArr) => {
+ let removed = [],
+ pushToRemove = arr.forEach((v, i) => (pullArr.includes(v) ? removed.push(v) : v)),
+ mutateTo = arr.filter((v, i) => !pullArr.includes(v));
+ arr.length = 0;
+ mutateTo.forEach(v => arr.push(v));
+ return removed;
+};
+
const pullBy = (arr, ...args) => {
+ const length = args.length;
+ let fn = length > 1 ? args[length - 1] : undefined;
+ fn = typeof fn == 'function' ? (args.pop(), fn) : undefined;
+ let argState = (Array.isArray(args[0]) ? args[0] : args).map(val => fn(val));
+ let pulled = arr.filter((v, i) => !argState.includes(fn(v)));
+ arr.length = 0;
+ pulled.forEach(v => arr.push(v));
+};
+
const radsToDegrees = rad => (rad * 180.0) / Math.PI;
+
const randomHexColorCode = () => {
+ let n = (Math.random() * 0xfffff * 1000000).toString(16);
+ return '#' + n.slice(0, 6);
+};
+
const randomIntArrayInRange = (min, max, n = 1) =>
+ Array.from({ length: n }, () => Math.floor(Math.random() * (max - min + 1)) + min);
+
const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
+
const randomNumberInRange = (min, max) => Math.random() * (max - min) + min;
+
+const readFileLines = filename =>
+ fs
+ .readFileSync(filename)
+ .toString('UTF8')
+ .split('\n');
+
const rearg = (fn, indexes) => (...args) => fn(...indexes.map(i => args[i]));
+
const recordAnimationFrames = (callback, autoStart = true) => {
+ let running = true,
+ raf;
+ const stop = () => {
+ running = false;
+ cancelAnimationFrame(raf);
+ };
+ const start = () => {
+ running = true;
+ run();
+ };
+ const run = () => {
+ raf = requestAnimationFrame(() => {
+ callback();
+ if (running) run();
+ });
+ };
+ if (autoStart) start();
+ return { start, stop };
+};
+
const redirect = (url, asLink = true) =>
+ asLink ? (window.location.href = url) : window.location.replace(url);
+
const reducedFilter = (data, keys, fn) =>
+ data.filter(fn).map(el =>
+ keys.reduce((acc, key) => {
+ acc[key] = el[key];
+ return acc;
+ }, {})
+ );
+
const reduceSuccessive = (arr, fn, acc) =>
+ arr.reduce((res, val, i, arr) => (res.push(fn(res.slice(-1)[0], val, i, arr)), res), [acc]);
+
const reduceWhich = (arr, comparator = (a, b) => a - b) =>
+ arr.reduce((a, b) => (comparator(a, b) >= 0 ? b : a));
+
const reject = (pred, array) => array.filter((...args) => !pred(...args));
+
const remove = (arr, func) =>
+ Array.isArray(arr)
+ ? arr.filter(func).reduce((acc, val) => {
+ arr.splice(arr.indexOf(val), 1);
+ return acc.concat(val);
+ }, [])
+ : [];
+
const removeNonASCII = str => str.replace(/[^\x20-\x7E]/g, '');
+
const renameKeys = (keysMap, obj) =>
+ Object.keys(obj).reduce(
+ (acc, key) => ({
+ ...acc,
+ ...{ [keysMap[key] || key]: obj[key] }
+ }),
+ {}
+ );
+
const reverseString = str => [...str].reverse().join('');
+
const RGBToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
+
const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
+
const runAsync = fn => {
+ const worker = new Worker(
+ URL.createObjectURL(new Blob([`postMessage((${fn})());`]), {
+ type: 'application/javascript; charset=utf-8'
+ })
+ );
+ return new Promise((res, rej) => {
+ worker.onmessage = ({ data }) => {
+ res(data), worker.terminate();
+ };
+ worker.onerror = err => {
+ rej(err), worker.terminate();
+ };
+ });
+};
+
const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
+
const sample = arr => arr[Math.floor(Math.random() * arr.length)];
+
const sampleSize = ([...arr], n = 1) => {
+ let m = arr.length;
+ while (m) {
+ const i = Math.floor(Math.random() * m--);
+ [arr[m], arr[i]] = [arr[i], arr[m]];
+ }
+ return arr.slice(0, n);
+};
+
const scrollToTop = () => {
+ const c = document.documentElement.scrollTop || document.body.scrollTop;
+ if (c > 0) {
+ window.requestAnimationFrame(scrollToTop);
+ window.scrollTo(0, c - c / 8);
+ }
+};
+
const sdbm = str => {
+ let arr = str.split('');
+ return arr.reduce(
+ (hashCode, currentVal) =>
+ (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
+ 0
+ );
+};
+
const serializeCookie = (name, val) => `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
+
const setStyle = (el, ruleName, val) => (el.style[ruleName] = val);
+
const shallowClone = obj => Object.assign({}, obj);
+
const shank = (arr, index = 0, delCount = 0, ...elements) =>
+ arr
+ .slice(0, index)
+ .concat(elements)
+ .concat(arr.slice(index + delCount));
+
const show = (...el) => [...el].forEach(e => (e.style.display = ''));
+
const shuffle = ([...arr]) => {
+ let m = arr.length;
+ while (m) {
+ const i = Math.floor(Math.random() * m--);
+ [arr[m], arr[i]] = [arr[i], arr[m]];
+ }
+ return arr;
+};
+
const similarity = (arr, values) => arr.filter(v => values.includes(v));
+
const size = val =>
+ Array.isArray(val)
+ ? val.length
+ : val && typeof val === 'object'
+ ? val.size || val.length || Object.keys(val).length
+ : typeof val === 'string'
+ ? new Blob([val]).size
+ : 0;
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+
const smoothScroll = element =>
+ document.querySelector(element).scrollIntoView({
+ behavior: 'smooth'
+ });
+
const sortCharactersInString = str => [...str].sort((a, b) => a.localeCompare(b)).join('');
+
const sortedIndex = (arr, n) => {
+ const isDescending = arr[0] > arr[arr.length - 1];
+ const index = arr.findIndex(el => (isDescending ? n >= el : n <= el));
+ return index === -1 ? arr.length : index;
+};
+
const sortedIndexBy = (arr, n, fn) => {
+ const isDescending = fn(arr[0]) > fn(arr[arr.length - 1]);
+ const val = fn(n);
+ const index = arr.findIndex(el => (isDescending ? val >= fn(el) : val <= fn(el)));
+ return index === -1 ? arr.length : index;
+};
+
const sortedLastIndex = (arr, n) => {
+ const isDescending = arr[0] > arr[arr.length - 1];
+ const index = arr.reverse().findIndex(el => (isDescending ? n <= el : n >= el));
+ return index === -1 ? 0 : arr.length - index;
+};
+
const sortedLastIndexBy = (arr, n, fn) => {
+ const isDescending = fn(arr[0]) > fn(arr[arr.length - 1]);
+ const val = fn(n);
+ const index = arr
+ .map(fn)
+ .reverse()
+ .findIndex(el => (isDescending ? val <= el : val >= el));
+ return index === -1 ? 0 : arr.length - index;
+};
+
const splitLines = str => str.split(/\r?\n/);
const spreadOver = fn => argsArr => fn(...argsArr);
-const stableSort = (arr, compare) =>
- arr
- .map((item, index) => ({ item, index }))
- .sort((a, b) => compare(a.item, b.item) || a.index - b.index)
- .map(({ item }) => item);
-const standardDeviation = (arr, usePopulation = false) => {
- const mean = arr.reduce((acc, val) => acc + val, 0) / arr.length;
- return Math.sqrt(
- arr.reduce((acc, val) => acc.concat((val - mean) ** 2), []).reduce((acc, val) => acc + val, 0) /
- (arr.length - (usePopulation ? 0 : 1))
- );
-};
-const stringPermutations = str => {
- if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
- return str
- .split('')
- .reduce(
- (acc, letter, i) =>
- acc.concat(stringPermutations(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)),
- []
- );
-};
-const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');
-const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);
-const sumBy = (arr, fn) =>
- arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0);
-const sumPower = (end, power = 2, start = 1) =>
- Array(end + 1 - start)
- .fill(0)
- .map((x, i) => (i + start) ** power)
- .reduce((a, b) => a + b, 0);
-const symmetricDifference = (a, b) => {
- const sA = new Set(a),
- sB = new Set(b);
- return [...a.filter(x => !sB.has(x)), ...b.filter(x => !sA.has(x))];
-};
-const symmetricDifferenceBy = (a, b, fn) => {
- const sA = new Set(a.map(v => fn(v))),
- sB = new Set(b.map(v => fn(v)));
- return [...a.filter(x => !sB.has(fn(x))), ...b.filter(x => !sA.has(fn(x)))];
-};
-const symmetricDifferenceWith = (arr, val, comp) => [
- ...arr.filter(a => val.findIndex(b => comp(a, b)) === -1),
- ...val.filter(a => arr.findIndex(b => comp(a, b)) === -1)
-];
-const tail = arr => (arr.length > 1 ? arr.slice(1) : arr);
-const take = (arr, n = 1) => arr.slice(0, n);
-const takeRight = (arr, n = 1) => arr.slice(arr.length - n, arr.length);
-const takeRightWhile = (arr, func) =>
- arr.reduceRight((acc, el) => (func(el) ? acc : [el, ...acc]), []);
-const takeWhile = (arr, func) => {
- for (const [i, val] of arr.entries()) if (func(val)) return arr.slice(0, i);
- return arr;
-};
-const throttle = (fn, wait) => {
- let inThrottle, lastFn, lastTime;
- return function() {
- const context = this,
- args = arguments;
- if (!inThrottle) {
- fn.apply(context, args);
- lastTime = Date.now();
- inThrottle = true;
- } else {
- clearTimeout(lastFn);
- lastFn = setTimeout(function() {
- if (Date.now() - lastTime >= wait) {
- fn.apply(context, args);
- lastTime = Date.now();
- }
- }, Math.max(wait - (Date.now() - lastTime), 0));
- }
- };
-};
-const timeTaken = callback => {
- console.time('timeTaken');
- const r = callback();
- console.timeEnd('timeTaken');
- return r;
-};
-const times = (n, fn, context = undefined) => {
- let i = 0;
- while (fn.call(context, i) !== false && ++i < n) {}
-};
-const toCamelCase = str => {
- let s =
- str &&
- str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
- .join('');
- return s.slice(0, 1).toLowerCase() + s.slice(1);
-};
-const toCurrency = (n, curr, LanguageFormat = undefined) =>
- Intl.NumberFormat(LanguageFormat, { style: 'currency', currency: curr }).format(n);
-const toDecimalMark = num => num.toLocaleString('en-US');
-const toHash = (object, key) =>
- Array.prototype.reduce.call(
- object,
- (acc, data, index) => ((acc[!key ? index : data[key]] = data), acc),
- {}
- );
-const toKebabCase = str =>
- str &&
- str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .map(x => x.toLowerCase())
- .join('-');
-const toOrdinalSuffix = num => {
- const int = parseInt(num),
- digits = [int % 10, int % 100],
- ordinals = ['st', 'nd', 'rd', 'th'],
- oPattern = [1, 2, 3, 4],
- tPattern = [11, 12, 13, 14, 15, 16, 17, 18, 19];
- return oPattern.includes(digits[0]) && !tPattern.includes(digits[1])
- ? int + ordinals[digits[0] - 1]
- : int + ordinals[3];
-};
-const toSafeInteger = num =>
- Math.round(Math.max(Math.min(num, Number.MAX_SAFE_INTEGER), Number.MIN_SAFE_INTEGER));
-const toSnakeCase = str =>
- str &&
- str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .map(x => x.toLowerCase())
- .join('_');
-const toTitleCase = str =>
- str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .map(x => x.charAt(0).toUpperCase() + x.slice(1))
- .join(' ');
-const toggleClass = (el, className) => el.classList.toggle(className);
-const tomorrow = (long = false) => {
- let t = new Date();
- t.setDate(t.getDate() + 1);
- const ret = `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(
- t.getDate()
- ).padStart(2, '0')}`;
- return !long ? ret : `${ret}T00:00:00`;
-};
-const transform = (obj, fn, acc) => Object.keys(obj).reduce((a, k) => fn(a, obj[k], k, obj), acc);
-const triggerEvent = (el, eventType, detail) =>
- el.dispatchEvent(new CustomEvent(eventType, { detail }));
-const truncateString = (str, num) =>
- str.length > num ? str.slice(0, num > 3 ? num - 3 : num) + '...' : str;
-const truthCheckCollection = (collection, pre) => collection.every(obj => obj[pre]);
-const unary = fn => val => fn(val);
-const uncurry = (fn, n = 1) => (...args) => {
- const next = acc => args => args.reduce((x, y) => x(y), acc);
- if (n > args.length) throw new RangeError('Arguments too few!');
- return next(fn)(args.slice(0, n));
-};
-const unescapeHTML = str =>
- str.replace(
- /&|<|>|'|"/g,
- tag =>
- ({
- '&': '&',
- '<': '<',
- '>': '>',
- ''': "'",
- '"': '"'
- }[tag] || tag)
- );
-const unflattenObject = obj =>
- Object.keys(obj).reduce((acc, k) => {
- if (k.indexOf('.') !== -1) {
- const keys = k.split('.');
- Object.assign(
- acc,
- JSON.parse(
- '{' +
- keys.map((v, i) => (i !== keys.length - 1 ? `"${v}":{` : `"${v}":`)).join('') +
- obj[k] +
- '}'.repeat(keys.length)
- )
- );
- } else acc[k] = obj[k];
- return acc;
- }, {});
-const unfold = (fn, seed) => {
- let result = [],
- val = [null, seed];
- while ((val = fn(val[1]))) result.push(val[0]);
- return result;
-};
-const union = (a, b) => Array.from(new Set([...a, ...b]));
-const unionBy = (a, b, fn) => {
- const s = new Set(a.map(fn));
- return Array.from(new Set([...a, ...b.filter(x => !s.has(fn(x)))]));
-};
-const unionWith = (a, b, comp) =>
- Array.from(new Set([...a, ...b.filter(x => a.findIndex(y => comp(x, y)) === -1)]));
-const uniqueElements = arr => [...new Set(arr)];
-const uniqueElementsBy = (arr, fn) =>
- arr.reduce((acc, v) => {
- if (!acc.some(x => fn(v, x))) acc.push(v);
- return acc;
- }, []);
-const uniqueElementsByRight = (arr, fn) =>
- arr.reduceRight((acc, v) => {
- if (!acc.some(x => fn(v, x))) acc.push(v);
- return acc;
- }, []);
-const uniqueSymmetricDifference = (a, b) => [
- ...new Set([...a.filter(v => !b.includes(v)), ...b.filter(v => !a.includes(v))])
-];
-const untildify = str => str.replace(/^~($|\/|\\)/, `${require('os').homedir()}$1`);
-const unzip = arr =>
- arr.reduce(
- (acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
- Array.from({
- length: Math.max(...arr.map(x => x.length))
- }).map(x => [])
- );
-const unzipWith = (arr, fn) =>
- arr
- .reduce(
- (acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
- Array.from({
- length: Math.max(...arr.map(x => x.length))
- }).map(x => [])
- )
- .map(val => fn(...val));
-const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;
-const when = (pred, whenTrue) => x => (pred(x) ? whenTrue(x) : x);
-const without = (arr, ...args) => arr.filter(v => !args.includes(v));
-const words = (str, pattern = /[^a-zA-Z-]+/) => str.split(pattern).filter(Boolean);
-const xProd = (a, b) => a.reduce((acc, x) => acc.concat(b.map(y => [x, y])), []);
-const yesNo = (val, def = false) =>
- /^(y|yes)$/i.test(val) ? true : /^(n|no)$/i.test(val) ? false : def;
-const zip = (...arrays) => {
- const maxLength = Math.max(...arrays.map(x => x.length));
- return Array.from({ length: maxLength }).map((_, i) => {
- return Array.from({ length: arrays.length }, (_, k) => arrays[k][i]);
- });
-};
-const zipObject = (props, values) =>
- props.reduce((obj, prop, index) => ((obj[prop] = values[index]), obj), {});
-const zipWith = (...array) => {
- const fn = typeof array[array.length - 1] === 'function' ? array.pop() : undefined;
- return Array.from(
- { length: Math.max(...array.map(a => a.length)) },
- (_, i) => (fn ? fn(...array.map(a => a[i])) : array.map(a => a[i]))
- );
-};
-const JSONToDate = arr => {
- const dt = new Date(parseInt(arr.toString().substr(6)));
- return `${dt.getDate()}/${dt.getMonth() + 1}/${dt.getFullYear()}`;
-};
-const binarySearch = (arr, val, start = 0, end = arr.length - 1) => {
- if (start > end) return -1;
- const mid = Math.floor((start + end) / 2);
- if (arr[mid] > val) return binarySearch(arr, val, start, mid - 1);
- if (arr[mid] < val) return binarySearch(arr, val, mid + 1, end);
- return mid;
-};
-const celsiusToFahrenheit = degrees => 1.8 * degrees + 32;
-const cleanObj = (obj, keysToKeep = [], childIndicator) => {
- Object.keys(obj).forEach(key => {
- if (key === childIndicator) {
- cleanObj(obj[key], keysToKeep, childIndicator);
- } else if (!keysToKeep.includes(key)) {
- delete obj[key];
- }
- });
- return obj;
-};
-const collatz = n => (n % 2 === 0 ? n / 2 : 3 * n + 1);
-const countVowels = str => (str.match(/[aeiou]/gi) || []).length;
-const factors = (num, primes = false) => {
- const isPrime = num => {
- const boundary = Math.floor(Math.sqrt(num));
- for (var i = 2; i <= boundary; i++) if (num % i === 0) return false;
- return num >= 2;
- };
- const isNeg = num < 0;
- num = isNeg ? -num : num;
- let array = Array.from({ length: num - 1 })
- .map((val, i) => (num % (i + 2) === 0 ? i + 2 : false))
- .filter(val => val);
- if (isNeg)
- array = array.reduce((acc, val) => {
- acc.push(val);
- acc.push(-val);
- return acc;
- }, []);
- return primes ? array.filter(isPrime) : array;
-};
-const fahrenheitToCelsius = degrees => (degrees - 32) * 5/9;
-const fibonacciCountUntilNum = num =>
- Math.ceil(Math.log(num * Math.sqrt(5) + 1 / 2) / Math.log((Math.sqrt(5) + 1) / 2));
-const fibonacciUntilNum = num => {
- let n = Math.ceil(Math.log(num * Math.sqrt(5) + 1 / 2) / Math.log((Math.sqrt(5) + 1) / 2));
- return Array.from({ length: n }).reduce(
- (acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i),
- []
- );
-};
-const heronArea = (side_a, side_b, side_c) => {
- const p = (side_a + side_b + side_c) / 2
- return Math.sqrt(p * (p-side_a) * (p-side_b) * (p-side_c))
- };
-const howManyTimes = (num, divisor) => {
- if (divisor === 1 || divisor === -1) return Infinity;
- if (divisor === 0) return 0;
- let i = 0;
- while (Number.isInteger(num / divisor)) {
- i++;
- num = num / divisor;
- }
- return i;
-};
-const httpDelete = (url, callback, err = console.error) => {
- const request = new XMLHttpRequest();
- request.open('DELETE', url, true);
- request.onload = () => callback(request);
- request.onerror = () => err(request);
- request.send();
-};
-const httpPut = (url, data, callback, err = console.error) => {
- const request = new XMLHttpRequest();
- request.open("PUT", url, true);
- request.setRequestHeader('Content-type','application/json; charset=utf-8');
- request.onload = () => callback(request);
- request.onerror = () => err(request);
- request.send(data);
-};
-const isArmstrongNumber = digits =>
- (arr => arr.reduce((a, d) => a + parseInt(d) ** arr.length, 0) == digits)(
- (digits + '').split('')
- );
-const isSimilar = (pattern, str) =>
- [...str].reduce(
- (matchIndex, char) =>
- char.toLowerCase() === (pattern[matchIndex] || '').toLowerCase()
- ? matchIndex + 1
- : matchIndex,
- 0
- ) === pattern.length;
-const kmphToMph = (kmph) => 0.621371192 * kmph;
-const levenshteinDistance = (string1, string2) => {
- if (string1.length === 0) return string2.length;
- if (string2.length === 0) return string1.length;
- let matrix = Array(string2.length + 1)
- .fill(0)
- .map((x, i) => [i]);
- matrix[0] = Array(string1.length + 1)
- .fill(0)
- .map((x, i) => i);
- for (let i = 1; i <= string2.length; i++) {
- for (let j = 1; j <= string1.length; j++) {
- if (string2[i - 1] === string1[j - 1]) {
- matrix[i][j] = matrix[i - 1][j - 1];
- } else {
- matrix[i][j] = Math.min(
- matrix[i - 1][j - 1] + 1,
- matrix[i][j - 1] + 1,
- matrix[i - 1][j] + 1
- );
- }
- }
- }
- return matrix[string2.length][string1.length];
-};
-const mphToKmph = (mph) => 1.6093440006146922 * mph;
-const pipeLog = data => console.log(data) || data;
-const quickSort = ([n, ...nums], desc) =>
- isNaN(n)
- ? []
- : [
- ...quickSort(nums.filter(v => (desc ? v > n : v <= n)), desc),
- n,
- ...quickSort(nums.filter(v => (!desc ? v > n : v <= n)), desc)
- ];
-const removeVowels = (str, repl = '') => str.replace(/[aeiou]/gi, repl);
-const solveRPN = rpn => {
- const OPERATORS = {
- '*': (a, b) => a * b,
- '+': (a, b) => a + b,
- '-': (a, b) => a - b,
- '/': (a, b) => a / b,
- '**': (a, b) => a ** b
- };
- const [stack, solve] = [
- [],
- rpn
- .replace(/\^/g, '**')
- .split(/\s+/g)
- .filter(el => !/\s+/.test(el) && el !== '')
- ];
- solve.forEach(symbol => {
- if (!isNaN(parseFloat(symbol)) && isFinite(symbol)) {
- stack.push(symbol);
- } else if (Object.keys(OPERATORS).includes(symbol)) {
- const [a, b] = [stack.pop(), stack.pop()];
- stack.push(OPERATORS[symbol](parseFloat(b), parseFloat(a)));
- } else {
- throw `${symbol} is not a recognized symbol`;
- }
- });
- if (stack.length === 1) return stack.pop();
- else throw `${rpn} is not a proper RPN. Please check it and try again`;
-};
-const speechSynthesis = message => {
- const msg = new SpeechSynthesisUtterance(message);
- msg.voice = window.speechSynthesis.getVoices()[0];
- window.speechSynthesis.speak(msg);
-};
-const squareSum = (...args) => args.reduce((squareSum, number) => squareSum + Math.pow(number, 2), 0);
+
const stableSort = (arr, compare) =>
+ arr
+ .map((item, index) => ({ item, index }))
+ .sort((a, b) => compare(a.item, b.item) || a.index - b.index)
+ .map(({ item }) => item);
+
const standardDeviation = (arr, usePopulation = false) => {
+ const mean = arr.reduce((acc, val) => acc + val, 0) / arr.length;
+ return Math.sqrt(
+ arr.reduce((acc, val) => acc.concat((val - mean) ** 2), []).reduce((acc, val) => acc + val, 0) /
+ (arr.length - (usePopulation ? 0 : 1))
+ );
+};
+
const stringPermutations = str => {
+ if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
+ return str
+ .split('')
+ .reduce(
+ (acc, letter, i) =>
+ acc.concat(stringPermutations(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)),
+ []
+ );
+};
+
const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');
+
const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);
+
const sumBy = (arr, fn) =>
+ arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0);
+
const sumPower = (end, power = 2, start = 1) =>
+ Array(end + 1 - start)
+ .fill(0)
+ .map((x, i) => (i + start) ** power)
+ .reduce((a, b) => a + b, 0);
+
const symmetricDifference = (a, b) => {
+ const sA = new Set(a),
+ sB = new Set(b);
+ return [...a.filter(x => !sB.has(x)), ...b.filter(x => !sA.has(x))];
+};
+
const symmetricDifferenceBy = (a, b, fn) => {
+ const sA = new Set(a.map(v => fn(v))),
+ sB = new Set(b.map(v => fn(v)));
+ return [...a.filter(x => !sB.has(fn(x))), ...b.filter(x => !sA.has(fn(x)))];
+};
+
const symmetricDifferenceWith = (arr, val, comp) => [
+ ...arr.filter(a => val.findIndex(b => comp(a, b)) === -1),
+ ...val.filter(a => arr.findIndex(b => comp(a, b)) === -1)
+];
+
const tail = arr => (arr.length > 1 ? arr.slice(1) : arr);
+
const take = (arr, n = 1) => arr.slice(0, n);
+
const takeRight = (arr, n = 1) => arr.slice(arr.length - n, arr.length);
+
const takeRightWhile = (arr, func) =>
+ arr.reduceRight((acc, el) => (func(el) ? acc : [el, ...acc]), []);
+
const takeWhile = (arr, func) => {
+ for (const [i, val] of arr.entries()) if (func(val)) return arr.slice(0, i);
+ return arr;
+};
+
const throttle = (fn, wait) => {
+ let inThrottle, lastFn, lastTime;
+ return function() {
+ const context = this,
+ args = arguments;
+ if (!inThrottle) {
+ fn.apply(context, args);
+ lastTime = Date.now();
+ inThrottle = true;
+ } else {
+ clearTimeout(lastFn);
+ lastFn = setTimeout(function() {
+ if (Date.now() - lastTime >= wait) {
+ fn.apply(context, args);
+ lastTime = Date.now();
+ }
+ }, Math.max(wait - (Date.now() - lastTime), 0));
+ }
+ };
+};
+
const times = (n, fn, context = undefined) => {
+ let i = 0;
+ while (fn.call(context, i) !== false && ++i < n) {}
+};
+
const timeTaken = callback => {
+ console.time('timeTaken');
+ const r = callback();
+ console.timeEnd('timeTaken');
+ return r;
+};
+
const toCamelCase = str => {
+ let s =
+ str &&
+ str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
+ .join('');
+ return s.slice(0, 1).toLowerCase() + s.slice(1);
+};
+
const toCurrency = (n, curr, LanguageFormat = undefined) =>
+ Intl.NumberFormat(LanguageFormat, { style: 'currency', currency: curr }).format(n);
+
const toDecimalMark = num => num.toLocaleString('en-US');
+
const toggleClass = (el, className) => el.classList.toggle(className);
+
const toHash = (object, key) =>
+ Array.prototype.reduce.call(
+ object,
+ (acc, data, index) => ((acc[!key ? index : data[key]] = data), acc),
+ {}
+ );
+
const toKebabCase = str =>
+ str &&
+ str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .map(x => x.toLowerCase())
+ .join('-');
+
const tomorrow = (long = false) => {
+ let t = new Date();
+ t.setDate(t.getDate() + 1);
+ const ret = `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}-${String(
+ t.getDate()
+ ).padStart(2, '0')}`;
+ return !long ? ret : `${ret}T00:00:00`;
+};
+
const toOrdinalSuffix = num => {
+ const int = parseInt(num),
+ digits = [int % 10, int % 100],
+ ordinals = ['st', 'nd', 'rd', 'th'],
+ oPattern = [1, 2, 3, 4],
+ tPattern = [11, 12, 13, 14, 15, 16, 17, 18, 19];
+ return oPattern.includes(digits[0]) && !tPattern.includes(digits[1])
+ ? int + ordinals[digits[0] - 1]
+ : int + ordinals[3];
+};
+
const toSafeInteger = num =>
+ Math.round(Math.max(Math.min(num, Number.MAX_SAFE_INTEGER), Number.MIN_SAFE_INTEGER));
+
const toSnakeCase = str =>
+ str &&
+ str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .map(x => x.toLowerCase())
+ .join('_');
+
const toTitleCase = str =>
+ str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .map(x => x.charAt(0).toUpperCase() + x.slice(1))
+ .join(' ');
+
const transform = (obj, fn, acc) => Object.keys(obj).reduce((a, k) => fn(a, obj[k], k, obj), acc);
+
const triggerEvent = (el, eventType, detail) =>
+ el.dispatchEvent(new CustomEvent(eventType, { detail }));
+
const truncateString = (str, num) =>
+ str.length > num ? str.slice(0, num > 3 ? num - 3 : num) + '...' : str;
+
const truthCheckCollection = (collection, pre) => collection.every(obj => obj[pre]);
+
const unary = fn => val => fn(val);
+
const uncurry = (fn, n = 1) => (...args) => {
+ const next = acc => args => args.reduce((x, y) => x(y), acc);
+ if (n > args.length) throw new RangeError('Arguments too few!');
+ return next(fn)(args.slice(0, n));
+};
+
const unescapeHTML = str =>
+ str.replace(
+ /&|<|>|'|"/g,
+ tag =>
+ ({
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ ''': "'",
+ '"': '"'
+ }[tag] || tag)
+ );
+
const unflattenObject = obj =>
+ Object.keys(obj).reduce((acc, k) => {
+ if (k.indexOf('.') !== -1) {
+ const keys = k.split('.');
+ Object.assign(
+ acc,
+ JSON.parse(
+ '{' +
+ keys.map((v, i) => (i !== keys.length - 1 ? `"${v}":{` : `"${v}":`)).join('') +
+ obj[k] +
+ '}'.repeat(keys.length)
+ )
+ );
+ } else acc[k] = obj[k];
+ return acc;
+ }, {});
+
const unfold = (fn, seed) => {
+ let result = [],
+ val = [null, seed];
+ while ((val = fn(val[1]))) result.push(val[0]);
+ return result;
+};
+
const union = (a, b) => Array.from(new Set([...a, ...b]));
+
const unionBy = (a, b, fn) => {
+ const s = new Set(a.map(fn));
+ return Array.from(new Set([...a, ...b.filter(x => !s.has(fn(x)))]));
+};
+
const unionWith = (a, b, comp) =>
+ Array.from(new Set([...a, ...b.filter(x => a.findIndex(y => comp(x, y)) === -1)]));
+
const uniqueElements = arr => [...new Set(arr)];
+
const uniqueElementsBy = (arr, fn) =>
+ arr.reduce((acc, v) => {
+ if (!acc.some(x => fn(v, x))) acc.push(v);
+ return acc;
+ }, []);
+
const uniqueElementsByRight = (arr, fn) =>
+ arr.reduceRight((acc, v) => {
+ if (!acc.some(x => fn(v, x))) acc.push(v);
+ return acc;
+ }, []);
+
const uniqueSymmetricDifference = (a, b) => [
+ ...new Set([...a.filter(v => !b.includes(v)), ...b.filter(v => !a.includes(v))])
+];
+
const untildify = str => str.replace(/^~($|\/|\\)/, `${require('os').homedir()}$1`);
+
const unzip = arr =>
+ arr.reduce(
+ (acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
+ Array.from({
+ length: Math.max(...arr.map(x => x.length))
+ }).map(x => [])
+ );
+
const unzipWith = (arr, fn) =>
+ arr
+ .reduce(
+ (acc, val) => (val.forEach((v, i) => acc[i].push(v)), acc),
+ Array.from({
+ length: Math.max(...arr.map(x => x.length))
+ }).map(x => [])
+ )
+ .map(val => fn(...val));
+
const URLJoin = (...args) =>
+ args
+ .join('/')
+ .replace(/[\/]+/g, '/')
+ .replace(/^(.+):\//, '$1://')
+ .replace(/^file:/, 'file:/')
+ .replace(/\/(\?|&|#[^!])/g, '$1')
+ .replace(/\?/g, '&')
+ .replace('&', '?');
+
const UUIDGeneratorBrowser = () =>
+ ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
+ (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
+ );
+
+const UUIDGeneratorNode = () =>
+ ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
+ (c ^ (crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16)
+ );
+
const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;
+
const when = (pred, whenTrue) => x => (pred(x) ? whenTrue(x) : x);
+
const without = (arr, ...args) => arr.filter(v => !args.includes(v));
+
const words = (str, pattern = /[^a-zA-Z-]+/) => str.split(pattern).filter(Boolean);
+
const xProd = (a, b) => a.reduce((acc, x) => acc.concat(b.map(y => [x, y])), []);
+
const yesNo = (val, def = false) =>
+ /^(y|yes)$/i.test(val) ? true : /^(n|no)$/i.test(val) ? false : def;
+
const zip = (...arrays) => {
+ const maxLength = Math.max(...arrays.map(x => x.length));
+ return Array.from({ length: maxLength }).map((_, i) => {
+ return Array.from({ length: arrays.length }, (_, k) => arrays[k][i]);
+ });
+};
+
const zipObject = (props, values) =>
+ props.reduce((obj, prop, index) => ((obj[prop] = values[index]), obj), {});
+
const zipWith = (...array) => {
+ const fn = typeof array[array.length - 1] === 'function' ? array.pop() : undefined;
+ return Array.from(
+ { length: Math.max(...array.map(a => a.length)) },
+ (_, i) => (fn ? fn(...array.map(a => a[i])) : array.map(a => a[i]))
+ );
+};
+
const binarySearch = (arr, val, start = 0, end = arr.length - 1) => {
+ if (start > end) return -1;
+ const mid = Math.floor((start + end) / 2);
+ if (arr[mid] > val) return binarySearch(arr, val, start, mid - 1);
+ if (arr[mid] < val) return binarySearch(arr, val, mid + 1, end);
+ return mid;
+};
+
const celsiusToFahrenheit = degrees => 1.8 * degrees + 32;
+
const cleanObj = (obj, keysToKeep = [], childIndicator) => {
+ Object.keys(obj).forEach(key => {
+ if (key === childIndicator) {
+ cleanObj(obj[key], keysToKeep, childIndicator);
+ } else if (!keysToKeep.includes(key)) {
+ delete obj[key];
+ }
+ });
+ return obj;
+};
+
const collatz = n => (n % 2 === 0 ? n / 2 : 3 * n + 1);
+
const countVowels = str => (str.match(/[aeiou]/gi) || []).length;
+
const factors = (num, primes = false) => {
+ const isPrime = num => {
+ const boundary = Math.floor(Math.sqrt(num));
+ for (var i = 2; i <= boundary; i++) if (num % i === 0) return false;
+ return num >= 2;
+ };
+ const isNeg = num < 0;
+ num = isNeg ? -num : num;
+ let array = Array.from({ length: num - 1 })
+ .map((val, i) => (num % (i + 2) === 0 ? i + 2 : false))
+ .filter(val => val);
+ if (isNeg)
+ array = array.reduce((acc, val) => {
+ acc.push(val);
+ acc.push(-val);
+ return acc;
+ }, []);
+ return primes ? array.filter(isPrime) : array;
+};
+
const fahrenheitToCelsius = degrees => (degrees - 32) * 5/9;
+
const fibonacciCountUntilNum = num =>
+ Math.ceil(Math.log(num * Math.sqrt(5) + 1 / 2) / Math.log((Math.sqrt(5) + 1) / 2));
+
const fibonacciUntilNum = num => {
+ let n = Math.ceil(Math.log(num * Math.sqrt(5) + 1 / 2) / Math.log((Math.sqrt(5) + 1) / 2));
+ return Array.from({ length: n }).reduce(
+ (acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i),
+ []
+ );
+};
+
const heronArea = (side_a, side_b, side_c) => {
+ const p = (side_a + side_b + side_c) / 2
+ return Math.sqrt(p * (p-side_a) * (p-side_b) * (p-side_c))
+ };
+
const howManyTimes = (num, divisor) => {
+ if (divisor === 1 || divisor === -1) return Infinity;
+ if (divisor === 0) return 0;
+ let i = 0;
+ while (Number.isInteger(num / divisor)) {
+ i++;
+ num = num / divisor;
+ }
+ return i;
+};
+
const httpDelete = (url, callback, err = console.error) => {
+ const request = new XMLHttpRequest();
+ request.open('DELETE', url, true);
+ request.onload = () => callback(request);
+ request.onerror = () => err(request);
+ request.send();
+};
+
const httpPut = (url, data, callback, err = console.error) => {
+ const request = new XMLHttpRequest();
+ request.open("PUT", url, true);
+ request.setRequestHeader('Content-type','application/json; charset=utf-8');
+ request.onload = () => callback(request);
+ request.onerror = () => err(request);
+ request.send(data);
+};
+
const isArmstrongNumber = digits =>
+ (arr => arr.reduce((a, d) => a + parseInt(d) ** arr.length, 0) == digits)(
+ (digits + '').split('')
+ );
+
const isSimilar = (pattern, str) =>
+ [...str].reduce(
+ (matchIndex, char) =>
+ char.toLowerCase() === (pattern[matchIndex] || '').toLowerCase()
+ ? matchIndex + 1
+ : matchIndex,
+ 0
+ ) === pattern.length;
+
const JSONToDate = arr => {
+ const dt = new Date(parseInt(arr.toString().substr(6)));
+ return `${dt.getDate()}/${dt.getMonth() + 1}/${dt.getFullYear()}`;
+};
+
const kmphToMph = (kmph) => 0.621371192 * kmph;
+
const levenshteinDistance = (string1, string2) => {
+ if (string1.length === 0) return string2.length;
+ if (string2.length === 0) return string1.length;
+ let matrix = Array(string2.length + 1)
+ .fill(0)
+ .map((x, i) => [i]);
+ matrix[0] = Array(string1.length + 1)
+ .fill(0)
+ .map((x, i) => i);
+ for (let i = 1; i <= string2.length; i++) {
+ for (let j = 1; j <= string1.length; j++) {
+ if (string2[i - 1] === string1[j - 1]) {
+ matrix[i][j] = matrix[i - 1][j - 1];
+ } else {
+ matrix[i][j] = Math.min(
+ matrix[i - 1][j - 1] + 1,
+ matrix[i][j - 1] + 1,
+ matrix[i - 1][j] + 1
+ );
+ }
+ }
+ }
+ return matrix[string2.length][string1.length];
+};
+
const mphToKmph = (mph) => 1.6093440006146922 * mph;
+
const pipeLog = data => console.log(data) || data;
+
const quickSort = ([n, ...nums], desc) =>
+ isNaN(n)
+ ? []
+ : [
+ ...quickSort(nums.filter(v => (desc ? v > n : v <= n)), desc),
+ n,
+ ...quickSort(nums.filter(v => (!desc ? v > n : v <= n)), desc)
+ ];
+
const removeVowels = (str, repl = '') => str.replace(/[aeiou]/gi, repl);
+
const solveRPN = rpn => {
+ const OPERATORS = {
+ '*': (a, b) => a * b,
+ '+': (a, b) => a + b,
+ '-': (a, b) => a - b,
+ '/': (a, b) => a / b,
+ '**': (a, b) => a ** b
+ };
+ const [stack, solve] = [
+ [],
+ rpn
+ .replace(/\^/g, '**')
+ .split(/\s+/g)
+ .filter(el => !/\s+/.test(el) && el !== '')
+ ];
+ solve.forEach(symbol => {
+ if (!isNaN(parseFloat(symbol)) && isFinite(symbol)) {
+ stack.push(symbol);
+ } else if (Object.keys(OPERATORS).includes(symbol)) {
+ const [a, b] = [stack.pop(), stack.pop()];
+ stack.push(OPERATORS[symbol](parseFloat(b), parseFloat(a)));
+ } else {
+ throw `${symbol} is not a recognized symbol`;
+ }
+ });
+ if (stack.length === 1) return stack.pop();
+ else throw `${rpn} is not a proper RPN. Please check it and try again`;
+};
+
const speechSynthesis = message => {
+ const msg = new SpeechSynthesisUtterance(message);
+ msg.voice = window.speechSynthesis.getVoices()[0];
+ window.speechSynthesis.speak(msg);
+};
+
const squareSum = (...args) => args.reduce((squareSum, number) => squareSum + Math.pow(number, 2), 0);
-module.exports = {CSVToArray,CSVToJSON,JSONToFile,JSONtoCSV,RGBToHex,URLJoin,UUIDGeneratorBrowser,UUIDGeneratorNode,all,allEqual,any,approximatelyEqual,arrayToCSV,arrayToHtmlList,ary,atob,attempt,average,averageBy,bifurcate,bifurcateBy,bind,bindAll,bindKey,binomialCoefficient,bottomVisible,btoa,byteSize,call,capitalize,capitalizeEveryWord,castArray,chainAsync,chunk,clampNumber,cloneRegExp,coalesce,coalesceFactory,collectInto,colorize,compact,compose,composeRight,converge,copyToClipboard,countBy,countOccurrences,counter,createElement,createEventHub,currentURL,curry,dayOfYear,debounce,decapitalize,deepClone,deepFlatten,deepFreeze,deepMapKeys,defaults,defer,degreesToRads,delay,detectDeviceType,difference,differenceBy,differenceWith,dig,digitize,distance,drop,dropRight,dropRightWhile,dropWhile,elementContains,elementIsVisibleInViewport,elo,equals,escapeHTML,escapeRegExp,everyNth,extendHex,factorial,fibonacci,filterFalsy,filterNonUnique,filterNonUniqueBy,findKey,findLast,findLastIndex,findLastKey,flatten,flattenObject,flip,forEachRight,forOwn,forOwnRight,formatDuration,fromCamelCase,functionName,functions,gcd,geometricProgression,get,getColonTimeFromDate,getDaysDiffBetweenDates,getImages,getMeridiemSuffixOfInteger,getScrollPosition,getStyle,getType,getURLParameters,groupBy,hammingDistance,hasClass,hasFlags,hashBrowser,hashNode,head,hexToRGB,hide,httpGet,httpPost,httpsRedirect,hz,inRange,indentString,indexOfAll,initial,initialize2DArray,initializeArrayWithRange,initializeArrayWithRangeRight,initializeArrayWithValues,initializeNDArray,insertAfter,insertBefore,intersection,intersectionBy,intersectionWith,invertKeyValues,is,isAbsoluteURL,isAfterDate,isAnagram,isArrayLike,isBeforeDate,isBoolean,isBrowser,isBrowserTabFocused,isDivisible,isDuplexStream,isEmpty,isEven,isFunction,isLowerCase,isNegativeZero,isNil,isNull,isNumber,isObject,isObjectLike,isPlainObject,isPrime,isPrimitive,isPromiseLike,isReadableStream,isSameDate,isSorted,isStream,isString,isSymbol,isTravisCI,isUndefined,isUpperCase,isValidJSON,isWritableStream,join,last,lcm,longestItem,lowercaseKeys,luhnCheck,mapKeys,mapObject,mapString,mapValues,mask,matches,matchesWith,maxBy,maxDate,maxN,median,memoize,merge,midpoint,minBy,minDate,minN,mostPerformant,negate,nest,nodeListToArray,none,nthArg,nthElement,objectFromPairs,objectToPairs,observeMutations,off,offset,omit,omitBy,on,onUserInputChange,once,orderBy,over,overArgs,pad,palindrome,parseCookie,partial,partialRight,partition,percentile,permutations,pick,pickBy,pipeAsyncFunctions,pipeFunctions,pluralize,powerset,prefix,prettyBytes,primes,promisify,pull,pullAtIndex,pullAtValue,pullBy,radsToDegrees,randomHexColorCode,randomIntArrayInRange,randomIntegerInRange,randomNumberInRange,readFileLines,rearg,recordAnimationFrames,redirect,reduceSuccessive,reduceWhich,reducedFilter,reject,remove,removeNonASCII,renameKeys,reverseString,round,runAsync,runPromisesInSeries,sample,sampleSize,scrollToTop,sdbm,serializeCookie,setStyle,shallowClone,shank,show,shuffle,similarity,size,sleep,smoothScroll,sortCharactersInString,sortedIndex,sortedIndexBy,sortedLastIndex,sortedLastIndexBy,splitLines,spreadOver,stableSort,standardDeviation,stringPermutations,stripHTMLTags,sum,sumBy,sumPower,symmetricDifference,symmetricDifferenceBy,symmetricDifferenceWith,tail,take,takeRight,takeRightWhile,takeWhile,throttle,timeTaken,times,toCamelCase,toCurrency,toDecimalMark,toHash,toKebabCase,toOrdinalSuffix,toSafeInteger,toSnakeCase,toTitleCase,toggleClass,tomorrow,transform,triggerEvent,truncateString,truthCheckCollection,unary,uncurry,unescapeHTML,unflattenObject,unfold,union,unionBy,unionWith,uniqueElements,uniqueElementsBy,uniqueElementsByRight,uniqueSymmetricDifference,untildify,unzip,unzipWith,validateNumber,when,without,words,xProd,yesNo,zip,zipObject,zipWith,JSONToDate,binarySearch,celsiusToFahrenheit,cleanObj,collatz,countVowels,factors,fahrenheitToCelsius,fibonacciCountUntilNum,fibonacciUntilNum,heronArea,howManyTimes,httpDelete,httpPut,isArmstrongNumber,isSimilar,kmphToMph,levenshteinDistance,mphToKmph,pipeLog,quickSort,removeVowels,solveRPN,speechSynthesis,squareSum}
\ No newline at end of file
+module.exports = {all,allEqual,any,approximatelyEqual,arrayToCSV,arrayToHtmlList,ary,atob,attempt,average,averageBy,bifurcate,bifurcateBy,bind,bindAll,bindKey,binomialCoefficient,bottomVisible,btoa,byteSize,call,capitalize,capitalizeEveryWord,castArray,chainAsync,chunk,clampNumber,cloneRegExp,coalesce,coalesceFactory,collectInto,colorize,compact,compose,composeRight,converge,copyToClipboard,countBy,counter,countOccurrences,createDirIfNotExists,createElement,createEventHub,CSVToArray,CSVToJSON,currentURL,curry,dayOfYear,debounce,decapitalize,deepClone,deepFlatten,deepFreeze,deepMapKeys,defaults,defer,degreesToRads,delay,detectDeviceType,difference,differenceBy,differenceWith,dig,digitize,distance,drop,dropRight,dropRightWhile,dropWhile,elementContains,elementIsVisibleInViewport,elo,equals,escapeHTML,escapeRegExp,everyNth,extendHex,factorial,fibonacci,filterFalsy,filterNonUnique,filterNonUniqueBy,findKey,findLast,findLastIndex,findLastKey,flatten,flattenObject,flip,forEachRight,formatDuration,forOwn,forOwnRight,fromCamelCase,functionName,functions,gcd,geometricProgression,get,getColonTimeFromDate,getDaysDiffBetweenDates,getImages,getMeridiemSuffixOfInteger,getScrollPosition,getStyle,getType,getURLParameters,groupBy,hammingDistance,hasClass,hasFlags,hashBrowser,hashNode,head,hexToRGB,hide,httpGet,httpPost,httpsRedirect,hz,indentString,indexOfAll,initial,initialize2DArray,initializeArrayWithRange,initializeArrayWithRangeRight,initializeArrayWithValues,initializeNDArray,inRange,insertAfter,insertBefore,intersection,intersectionBy,intersectionWith,invertKeyValues,is,isAbsoluteURL,isAfterDate,isAnagram,isArrayLike,isBeforeDate,isBoolean,isBrowser,isBrowserTabFocused,isDivisible,isDuplexStream,isEmpty,isEven,isFunction,isLowerCase,isNegativeZero,isNil,isNull,isNumber,isObject,isObjectLike,isPlainObject,isPrime,isPrimitive,isPromiseLike,isReadableStream,isSameDate,isSorted,isStream,isString,isSymbol,isTravisCI,isUndefined,isUpperCase,isValidJSON,isWritableStream,join,JSONtoCSV,JSONToFile,last,lcm,longestItem,lowercaseKeys,luhnCheck,mapKeys,mapObject,mapString,mapValues,mask,matches,matchesWith,maxBy,maxDate,maxN,median,memoize,merge,midpoint,minBy,minDate,minN,mostPerformant,negate,nest,nodeListToArray,none,nthArg,nthElement,objectFromPairs,objectToPairs,observeMutations,off,offset,omit,omitBy,on,once,onUserInputChange,orderBy,over,overArgs,pad,palindrome,parseCookie,partial,partialRight,partition,percentile,permutations,pick,pickBy,pipeAsyncFunctions,pipeFunctions,pluralize,powerset,prefix,prettyBytes,primes,promisify,pull,pullAtIndex,pullAtValue,pullBy,radsToDegrees,randomHexColorCode,randomIntArrayInRange,randomIntegerInRange,randomNumberInRange,readFileLines,rearg,recordAnimationFrames,redirect,reducedFilter,reduceSuccessive,reduceWhich,reject,remove,removeNonASCII,renameKeys,reverseString,RGBToHex,round,runAsync,runPromisesInSeries,sample,sampleSize,scrollToTop,sdbm,serializeCookie,setStyle,shallowClone,shank,show,shuffle,similarity,size,sleep,smoothScroll,sortCharactersInString,sortedIndex,sortedIndexBy,sortedLastIndex,sortedLastIndexBy,splitLines,spreadOver,stableSort,standardDeviation,stringPermutations,stripHTMLTags,sum,sumBy,sumPower,symmetricDifference,symmetricDifferenceBy,symmetricDifferenceWith,tail,take,takeRight,takeRightWhile,takeWhile,throttle,times,timeTaken,toCamelCase,toCurrency,toDecimalMark,toggleClass,toHash,toKebabCase,tomorrow,toOrdinalSuffix,toSafeInteger,toSnakeCase,toTitleCase,transform,triggerEvent,truncateString,truthCheckCollection,unary,uncurry,unescapeHTML,unflattenObject,unfold,union,unionBy,unionWith,uniqueElements,uniqueElementsBy,uniqueElementsByRight,uniqueSymmetricDifference,untildify,unzip,unzipWith,URLJoin,UUIDGeneratorBrowser,UUIDGeneratorNode,validateNumber,when,without,words,xProd,yesNo,zip,zipObject,zipWith,binarySearch,celsiusToFahrenheit,cleanObj,collatz,countVowels,factors,fahrenheitToCelsius,fibonacciCountUntilNum,fibonacciUntilNum,heronArea,howManyTimes,httpDelete,httpPut,isArmstrongNumber,isSimilar,JSONToDate,kmphToMph,levenshteinDistance,mphToKmph,pipeLog,quickSort,removeVowels,solveRPN,speechSynthesis,squareSum}
\ No newline at end of file
diff --git a/test/createDirIfNotExists.js b/test/createDirIfNotExists.js
new file mode 100644
index 000000000..3c7fcb184
--- /dev/null
+++ b/test/createDirIfNotExists.js
@@ -0,0 +1,6 @@
+const expect = require('expect');
+const {createDirIfNotExists} = require('./_30s.js');
+
+test('createDirIfNotExists is a Function', () => {
+ expect(createDirIfNotExists).toBeInstanceOf(Function);
+});