aid.js
Version:
A Bundle of Javascript util Library to help developers. No dependency to other Libraries.
1,757 lines (1,480 loc) • 158 kB
JavaScript
/*
* aid.js 0.1.96
* https://www.npmjs.com/package/aid.js
*
* The MIT License (MIT)
* Copyright (c) 2016-2019 Hyun-Seok.Kim, dragmove@gmail.com
*/
(function() {
'use strict';
// Establish the global object, `window` (`self`) in the browser, `global`
// on the server, or `this` in some virtual machines. We use `self`
// instead of `window` for `WebWorker` support.
var global =
(typeof self == 'object' && self.self === self && self) ||
(typeof global == 'object' && global.global === global && global) ||
this ||
{};
var _slice = Array.prototype.slice;
var _hasOwnProperty = Object.prototype.hasOwnProperty;
var aid = {},
operator = {},
platform = {},
browser = {},
string = {},
math = {},
date = {},
array = {},
object = {},
element = {},
file = {},
clipboard = {},
monad = {}; // https://en.wikipedia.org/wiki/Monad_(functional_programming)
/**
* is null/undefined or other
*
* @static
* @method existy
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.existy(undefined) ); // false
* console.log( aid.existy(null) ); // false
* console.log( aid.existy('') ); // true
*/
aid.existy = function existy(any) {
return any != null;
};
/**
* check defined
*
* @static
* @method isDefined
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isDefined(undefined) ); // false
* console.log( aid.isDefined(null) ); // false
* console.log( aid.isDefined(0) ); // true
*/
aid.isDefined = function isDefined(any) {
if (any === null || typeof any === 'undefined') return false;
return true;
};
/**
* check type is Boolean
*
* @static
* @method isBoolean
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isBoolean(false) ); // true
*/
aid.isBoolean = function isBoolean(any) {
if (!aid.isDefined(any)) return false;
return any.constructor === Boolean;
};
/**
* check type is Number
*
* @static
* @method isNumber
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isNumber(-1) ); // true
*/
aid.isNumber = function isNumber(any) {
if (!aid.isDefined(any)) return false;
return !isNaN(any) && any.constructor === Number;
};
/**
* check type is Integer Number
*
* @static
* @method isInteger
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isInteger(-1) ); // true
*/
aid.isInteger = function isInteger(any) {
if (!aid.isNumber(any)) return false;
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
return isFinite(any) && Math.floor(any) === any;
};
/**
* check type is String
*
* @static
* @method isString
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isString('') ); // true
*/
aid.isString = function isString(any) {
if (!aid.isDefined(any)) return false;
return any.constructor === String;
};
/**
* check type is Array
*
* @static
* @method isArray
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isArray([]) ); // true
*/
aid.isArray = function isArray(any) {
if (!aid.isDefined(any)) return false;
return any.constructor === Array;
};
/**
* check type is Object
*
* @static
* @method isObject
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isObject({}) ); // true
*/
aid.isObject = function isObject(any) {
if (!aid.isDefined(any)) return false;
return any.constructor === Object;
};
/**
* check type is Function
*
* @static
* @method isFunction
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isFunction(function(){}) ); // true
*/
aid.isFunction = function isFunction(any) {
if (!aid.isDefined(any)) return false;
return any.constructor === Function;
};
/**
* check type is RegExp
*
* @static
* @method isRegExp
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isRegExp(new RegExp('^aid')) ); // true
* console.log( aid.isRegExp(/^aid/) ); // true
*/
aid.isRegExp = function isRegExp(any) {
if (!aid.isDefined(any)) return false;
return any.constructor === RegExp;
};
/**
* check type is Error (can check Error, EvalError, InternalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError)
* refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
*
* @static
* @method isError
* @param {*} any
* @param {Constructor} errorType (optional)
* @returns {Boolean} return boolean
* @example
* console.log( aid.isError(new Error('msg')) ); // true
* console.log( aid.isError(new TypeError('msg')) ); // true
* console.log( aid.isError(new RangeError('msg')) ); // true
* console.log( aid.isError(new TypeError('msg'), TypeError) ); // true
* console.log( aid.isError(new TypeError('msg'), Error) ); // false
* console.log( aid.isError(new TypeError('msg'), RangeError) ); // false
*/
aid.isError = function isError(any, errorType) {
if (!aid.isDefined(any)) return false;
var con = any.constructor;
if (!aid.isDefined(errorType)) {
// Non-standard - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/InternalError
if (window.InternalError && con === window.InternalError) return true;
return (
con === Error ||
con === EvalError ||
con === RangeError ||
con === ReferenceError ||
con === SyntaxError ||
con === TypeError ||
con === URIError
);
}
return con === errorType;
};
/**
* check type is HTML element
* refer to https://developer.mozilla.org/en-US/docs/Web/API/Element
*
* @static
* @method isElement
* @param {*} any
* @returns {Boolean} return boolean
* @example
* console.log( aid.isElement(document.createElement('div')) ); // true
*/
aid.isElement = function isElement(any) {
return aid.isDefined(any) && typeof any === 'object' && any.nodeType === 1 && any instanceof Node;
};
/**
* extend function
*
* @static
* @method extend
* @param {Object} prototype of class function, or object has variables or methods.
* @param {Object} object has variables or methods
* @example
* function Destination() {
* this.name = 'destination';
* this.type = 'json';
* };
*
* var source = {
* getName: function() { return this.name; },
* getType: function() { return this.type; }
* };
*
* aid.extend(Destination.prototype, source);
*
* var destination = new Destination();
* console.log( destination.getName() ); // 'destination'
* console.log( destination.getType() ); // 'json'
*/
aid.extend = function extend(destination, source) {
if (!(destination instanceof Object) || !(typeof destination === 'object')) {
throw new TypeError('[aid.extend] Type of destination parameter must be instance of Object, and object.');
}
if (!(typeof source === 'object')) throw new TypeError('[aid.extend] Type of source parameter must be Object.');
for (var key in source) {
if (source.hasOwnProperty(key)) {
destination[key] = source[key];
}
}
return destination;
};
/**
* inherit Class function
*
* @static
* @method inherit
* @param {class} ChildClass - child class function
* @param {class} ParentClass - parent class function
* @example
*/
aid.inherit = (function() {
// use closure, protect gabarge collection.
var F = function() {};
return function inherit(ChildClass, ParentClass) {
F.prototype = ParentClass.prototype;
ChildClass.prototype = new F();
ChildClass.prototype.constructor = ChildClass;
ChildClass.super = ParentClass.prototype;
};
})();
/**
* create namespace
*
* @static
* @method namespace
* @param {String} namespace
* @param {Object} parent
* @returns {Object} return object
* @example
* aid.namespace('first.second.third'); // create first.second.third object
*/
aid.namespace = function(namespace, parent) {
if (!aid.isString(namespace)) throw new TypeError('[aid.namespace] Type of namespace parameter must be String.');
if (!(aid.isObject(parent) || !aid.isDefined(parent))) {
throw new TypeError('[aid.namespace] Type of parent parameter must be Object or null or undefined.');
}
var ns = parent || global;
if (namespace) {
var parts = namespace.split('.');
parts.forEach(function(part) {
if (!ns[part]) ns[part] = {};
ns = ns[part];
});
}
return ns;
};
/**
* return memoized function
*
* @static
* @method memoize
* @param {Function} func
* @param {Function} hasher (optional)
* @returns {Function} return function
* @example
* var plus = function(a, b) { return a + b; };
* var memoized = aid.memoize(plus);
* console.log( memoized('aid', '.js') ); // aid.js
*/
aid.memoize = function memoize(func, hasher) {
if (!aid.isFunction(func)) throw new TypeError('[aid.memoize] Type of func parameter must be Function.');
if (aid.isDefined(hasher) && !aid.isFunction(hasher))
throw new TypeError('[aid.memoize] Type of hasher parameter must be undefined or null or Function.');
var memoized = function(_key) {
var cache = memoized.cache;
var key = hasher ? hasher.apply(null, _slice.call(arguments)) : _key;
if (cache.has(key)) return cache.get(key);
var result = func.apply(null, _slice.call(arguments));
cache.set(key, result);
return result;
};
memoized.cache = aid.createDictionary();
return memoized;
};
/**
* borrow method from donor object
*
* @static
* @method borrow
* @param {Object} borrower
* @param {Object} donor
* @param {String} funcName
* @example
* var borrower = {}, donor = { say: function() { return 'hello, world'; } };
* aid.borrow(borrower, donor, 'say');
* console.log( borrower.say() ); // 'hello, world'
*/
aid.borrow = function borrow(borrower, donor, funcName) {
if (!aid.isObject(borrower) || !aid.isObject(donor))
throw new TypeError('[aid.borrow] Type of borrower, donor parameters must be Object.');
if (!aid.isString(funcName)) throw new TypeError('[aid.borrow] Type of funcName parameter must be String.');
if (!aid.isDefined(donor[funcName]))
throw new Error('[aid.borrow] donor object parameter has not function with funcName parameter.');
if (aid.isDefined(borrower[funcName]))
throw new Error('[aid.borrow] borrower object parameter already has function with funcName parameter.');
borrower[funcName] = function() {
var args = _slice.call(arguments);
return donor[funcName].apply(this, args);
};
};
/**
* return function bind context and parameters
*
* @static
* @method bind
* @param {Function} func
* @param {Object} context
* @returns {Function} return function
* @example
* this.title = 'global - aid.js';
* var obj = { title: 'obj - aid.js', getTitle: function() { return this.title; } };
* var getObjTitle = bind(obj.getTitle, obj), getGlobalTitle = bind(obj.getTitle, window);
* console.log( getObjTitle() ); // 'obj - aid.js';
* console.log( getGlobalTitle() ); // 'global - aid.js';
*/
aid.bind = function bind(func, context) {
if (!aid.isFunction(func)) throw new TypeError('[aid.bind] Type of func parameter must be Function.');
return function() {
return func.apply(context, arguments);
};
};
/**
* return function composed function_a and function_b
*
* @static
* @method compose
* @param {Function} func_a
* @param {Function} func_b
* @returns {Function} return function
* @example
* var isNotNaN = aid.compose(aid.operator['!'], isNaN);
* console.log( isNotNaN(0) ); // true
*/
aid.compose = function compose(func_a, func_b) {
if (!aid.isFunction(func_a) || !aid.isFunction(func_b)) {
throw new TypeError('[aid.compose] Type of func_a, func_b parameters must be Function.');
}
return function() {
return func_a(func_b.apply(null, arguments));
};
};
/**
* return function negate object parameter
*
* @static
* @method not
* @param {Function} func
* @returns {Function} return function
* @example
* var isNotNaN = aid.not(isNaN);
* console.log( isNotNaN(0) ); // true
*/
aid.not = function not(func) {
if (!aid.isFunction(func)) throw new TypeError('[aid.not] Type of func parameter must be Function.');
return function() {
return !func.apply(null, arguments);
};
};
/**
* each method for data can loop
*
* @static
* @method each
* @param {(Array|String)} iterable
* @param {Function} func
* @param {Object} context
* @example
* var result = [];
* aid.each('aid.js', function(v) { result.push(v); }, null);
* console.log( result.join('') ); // 'aid.js'
*/
aid.each = function each(iterable, func, context) {
if (!(aid.isArray(iterable) || aid.isString(iterable))) {
throw new TypeError('[aid.each] Type of iterable parameter must be Array or String.');
}
var _context = aid.existy(context) ? context : null;
for (var i = 0, max = iterable.length; i < max; i++) {
func.call(_context, iterable[i]);
}
};
/**
* check object is truthy
*
* @static
* @method truthy
* @param {Object} obj
* @returns {Boolean} return boolean
* @example
* console.log( aid.truthy(true) ); // true
* console.log( aid.truthy([]) ); // true
* console.log( aid.truthy(0) ); // false
* console.log( aid.truthy('') ); // false
*/
aid.truthy = function truthy(obj) {
return !!obj;
};
/**
* check object is falsy
*
* @static
* @method falsy
* @param {Object} obj
* @returns {Boolean} return boolean
* @example
* console.log( aid.falsy(true) ); // false
* console.log( aid.falsy([]) ); // false
* console.log( aid.falsy(0) ); // true
* console.log( aid.falsy('') ); // true
*/
aid.falsy = function falsy(obj) {
return !!!obj;
};
/**
* get indexed value of array or string
*
* @static
* @method nth
* @param {Array|String} iterable
* @param {Number} index
* @example
* console.log( aid.nth('string', 1) ); // 't'
* console.log( aid.nth('string', -1) ); // null
* console.log( aid.nth([0, 'str', true], 2) ); // true
* console.log( aid.nth([0, 'str', true], 99) ); // null
*/
aid.nth = function nth(iterable, index) {
if (!(aid.isArray(iterable) || aid.isString(iterable))) {
throw new TypeError('[aid.nth] Type of iterable parameter must be Array or String.');
}
if (!aid.isInteger(index)) throw new TypeError('[aid.nth] Type of index parameter must be Integer Number.');
return index < 0 || index > iterable.length - 1 ? null : iterable[index];
};
/**
* check all arguments are true
*
* @static
* @method allOf
* @returns {Boolean} return boolean
* @example
* console.log( aid.allOf(true, true) ); // true
* console.log( aid.allOf(true, false) ); // false
*/
aid.allOf = function allOf(/*args*/) {
var args = _slice.call(arguments);
return args.every(function(val) {
return val === true;
});
};
/**
* check some argument is true
*
* @static
* @method anyOf
* @returns {Boolean} return boolean
* @example
* console.log( anyOf(true, false) ); // true
* console.log( anyOf(false, false) ); // false
*/
aid.anyOf = function anyOf(/*args*/) {
var args = _slice.call(arguments);
return args.some(function(val) {
return val === true;
});
};
/**
* return function always return constant value
* http://underscorejs.org/#constant
*
* @static
* @method constant
* @param {Object} obj
* @returns {Function} return function
* @example
* var obj = {name: 'aid.js'};
* console.log( aid.constant(obj)() === obj ); // true
*/
aid.constant = function constant(obj) {
return function() {
return obj;
};
};
/**
* return function pluck field of object, array, string
*
* @static
* @method plucker
* @param {(String|Number)} field of object, array, string
* @returns {Function} return function
* @example
* var getTitle = aid.plucker('title');
* var obj = {title: 'aid.js', description: 'A bundle of Javascript util Library for help developers. No dependency to other Libraries.'};
* console.log( getTitle(obj) ); // 'aid.js'
*
* var getFirst = aid.plucker(0);
* var str = 'aid.js';
* console.log( getFirst(str) ); // 'a'
*
* var getLength = aid.plucker('length');
* var arr = [1, 2, 3, 4, 5];
* console.log( getLength(arr) ); // 5
*/
aid.plucker = function plucker(field) {
if (!(aid.isString(field) || aid.isNumber(field))) {
throw new TypeError('[aid.plucker] Type of field parameter must be String or Number.');
}
return function(obj) {
if (!(aid.isObject(obj) || aid.isArray(obj) || aid.isString(obj))) {
throw new TypeError('[aid.plucker] Type of obj parameter must be Object or Array or String.');
}
return obj[field];
};
};
/**
* return best(optimized by condition function) value
*
* @static
* @method best
* @param {Function} condition function to find best value
* @param {Array} array
* @example
* console.log( aid.best(function(x, y) { return x > y; }, [2, 4, 1, 5, 3]) ); // 5
*/
aid.best = function best(conditionFunc, array) {
if (!aid.isFunction(conditionFunc))
throw new TypeError('[aid.best] Type of conditionFunc parameter must be Function.');
if (!aid.isArray(array)) throw new TypeError('[aid.best] Type of array parameter must be Array.');
return array.reduce(function(previousValue, currentValue) {
return conditionFunc(previousValue, currentValue) ? previousValue : currentValue;
});
};
/**
* return array has values filtered
*
* @static
* @method iterateUntil
* @param {Function} function return value
* @param {Function} function has condition
* @param {Object} initial value
* @returns {Array} return array has values filtered
* @example
* console.log( aid.iterateUntil(function(n) { return n + n; }, function(n) { return n <= 1042 }, 1) ); // [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
*/
aid.iterateUntil = function iterateUntil(calculateFunc, conditionFunc, initialValue) {
if (!aid.isFunction(calculateFunc))
throw new TypeError('[aid.iterateUntil] Type of calculateFunc parameter must be Function.');
if (!aid.isFunction(conditionFunc))
throw new TypeError('[aid.iterateUntil] Type of conditionFunc parameter must be Function.');
var array = [],
result = calculateFunc(initialValue);
while (conditionFunc(result)) {
array.push(result);
result = calculateFunc(result);
}
return array;
};
/**
* curry function can use one parameter
*
* @static
* @method curry
* @param {Function} function
* @returns {Function} return function
* @example
*/
aid.curry = function curry(func) {
if (!aid.isFunction(func)) throw new TypeError('[aid.curry] Type of func parameter must be Function.');
return function(arg) {
return func(arg);
};
};
/**
* curry function can use two parameter
*
* @static
* @method curry2
* @param {Function} function
* @returns {Function} return function
* @example
* var parseBinaryStr = aid.curry2(parseInt)(2);
* console.log( parseBinaryStr('111') ); // 7
*/
aid.curry2 = function curry2(func) {
if (!aid.isFunction(func)) throw new TypeError('[aid.curry2] Type of func parameter must be Function.');
return function(secondArg) {
return function(firstArg) {
return func(firstArg, secondArg);
};
};
};
/**
* curry function for variadic functions
*
* @static
* @method curryAll
* @param {Function} function
* @param {Number} minimum number of arguments to curry
* @returns {Function} return function
* @example
* console.log( curryAll(Math.max, 2)(1, 99) ); // 99
* console.log( curryAll(Math.max, 2)(1)(99) ); // 99
*/
aid.curryAll = function curryAll(func, curryArgsNum) {
if (!aid.isFunction(func)) throw new TypeError('[aid.curryAll] Type of func parameter must be Function.');
var arity = curryArgsNum || func.length;
return function curried() {
var args = _slice.call(arguments),
context = this;
return args.length >= arity
? func.apply(context, args)
: function() {
var rest = _slice.call(arguments);
return curried.apply(context, args.concat(rest));
};
};
};
/**
* reverse arguments of function
* refer to https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch3.md/#reversing-arguments
*
* @static
* @method reverseArgs
* @param {Function} function
* @returns {Function} return function
* @example
* var getReverseArgs = aid.reverseArgs(function() { return Array.prototype.slice.call(arguments); });
* console.log( getReverseArgs(99, 'aid.js') ); // ['aid.js', 99]
*/
aid.reverseArgs = function reverseArgs(func) {
if (!aid.isFunction(func)) throw new TypeError('[aid.reverseArgs] Type of func parameter must be Function.');
return function(/* args... */) {
var args = _slice.call(arguments);
return func.apply(null, args.concat().reverse());
};
};
/**
* partial application
*
* @static
* @method partial
* @param {Function} function
* @returns {Function} return function
* @example
* function sum() {
* var args = Array.prototype.slice.call(arguments);
* return args.reduce(function(acc, cur) {
* return acc + cur;
* }, 0);
* }
* console.log( aid.partial(sum)() ); // 0
* console.log( aid.partial(sum, 1)() ); // 1
* console.log( aid.partial(sum, 1)(2) ); // 3
* console.log( aid.partial(sum)(1, 2, 3) ); // 6
*/
aid.partial = function partial(func /*, args... */) {
if (!aid.isFunction(func)) throw new TypeError('[aid.partial] Type of func parameter must be Function.');
var args = aid.rest(_slice.call(arguments));
return function(/* args... */) {
return func.apply(func, args.concat(_slice.call(arguments)));
};
};
/**
* partial application for the right arguments
*
* @static
* @method partialRight
* @param {Function} function
* @returns {Function} return function
* @example
* function three(str, num, arr) { return str + ' ' + num + ' ' + arr; }
* console.log( aid.partialRight(three, 99, [1, 2, 3])('aid.js') ); // 'aid.js 99 1,2,3'
* console.log( aid.partial( aid.partialRight(three, [1, 2, 3]), 'aid.js' )(99) ); // 'aid.js 99 1,2,3'
*/
aid.partialRight = function partialRight(func /*, args... */) {
if (!aid.isFunction(func)) throw new TypeError('[aid.partialRight] Type of func parameter must be Function.');
var args = aid.rest(_slice.call(arguments));
return function(/* args... */) {
return func.apply(null, _slice.call(arguments).concat(args));
};
};
/**
* rest
* refer to _.rest function of underscore.js - https://github.com/jashkenas/underscore/blob/master/underscore.js
*
* @static
* @method rest
* @param {Array} array
* @param {Number} begin index to slice arguments.
* @returns {Array} return array
* @example
* var array = [1, 2, 3, 4, 5];
* console.log( aid.rest(array) ); // [2, 3, 4, 5]
* console.log( aid.rest(array, 2) ); // [3, 4, 5]
*/
aid.rest = function rest(array, beginIndex) {
if (!aid.isArray(array)) throw new TypeError('[aid.rest] Type of array parameter must be Array.');
var begin = !aid.existy(beginIndex) ? 1 : beginIndex;
return _slice.call(array, begin);
};
/**
* pipeline
*
* @static
* @method pipeline
* @param {Object} seed
* @returns {Object} return value
* @example
* function negative(n) { return n * -1; }
* function half(n) { return n / 2; }
* function negativeHalf(n) { return aid.pipeline(n, negative, half); }
* console.log( aid.pipeline(80, negative) ); // -80
* console.log( negativeHalf(80) ); // 80 * -1 / 2
*/
aid.pipeline = function pipeline(seed /* args */) {
var restArgs = aid.rest(_slice.call(arguments));
aid.each(
restArgs,
function(value) {
if (!aid.isFunction(value)) throw new TypeError('[aid.pipeline] Type of rest parameters must be Function.');
},
null
);
return restArgs.reduce(function(prev, current) {
return current(prev);
}, seed);
};
/**
* lazyChain
*
* @static
* @method lazyChain
* @param {Object} object
* @returns {Object} return {invoke, force}
* @example
* var lazy = aid.lazyChange([2, 1, 3]).invoke('concat', [7, 7, 8, 9, 0]).invoke('sort');
* console.log( lazy.force() ); // [0, 1, 2, 3, 7, 7, 8, 9]
*
* // with aid.pipeline
* function double(array) { return array.map(function(v) { return v * 2; }); }
* function lazyReverseAndNegative(array) { return aid.lazyChain(array).invoke('reverse').invoke('map', function(v) { return v * -1; }); }
* console.log( aid.pipeline([1, 2, 3], double, lazyReverseAndNegative).force() ); // [-6, -4, -2]
*/
aid.lazyChain = function lazyChain(obj) {
var calls = [];
return {
invoke: function(methodName /*, args */) {
var args = aid.rest(_slice.call(arguments));
calls.push(function(target) {
var method = target[methodName];
if (!aid.isDefined(method)) {
throw Error('[aid.lazyChain] ' + target.constructor.name + ' has not ' + methodName + ' method.');
}
return method.apply(target, args);
});
return this;
},
force: function() {
return calls.reduce(function(ret, thunk) {
return thunk(ret);
}, obj);
},
};
};
/**
* check two parameters are equal
*
* @static
* @method eq
* @param {Object} obj
* @param {Object} obj
* @returns {Boolean} return boolean
* @example
* console.log( aid.eq(99)(99) ); // true
*/
aid.eq = aid.curry2(function(lhs, rhs) {
return lhs === rhs;
});
/**
* identity combinator
*
* @static
* @method identity
* @param {Object} value
* @returns {Object} return value
* @example
* TODO:
*/
aid.identity = function identity(value) {
return value;
};
/**
* tab combinator
*
* @static
* @method tab
* @param {Function} function
* @returns {Function} return function
* @example
* TODO:
*/
aid.tab = function tab(func) {
if (!aid.isFunction(func)) throw new TypeError('[aid.tab] Type of func parameter must be Function.');
return function(value) {
func(value);
return value;
};
};
/**
* alt combinator
*
* @static
* @method alt
* @param {Function} func_a
* @param {Function} func_b
* @returns {Function} return function
* @example
* var alt = aid.alt(function(val) { return val; }, function(val) { return !val; });
* console.log( alt(false) ); // true
*/
aid.alt = function alt(func_a, func_b) {
if (!aid.isFunction(func_a) || !aid.isFunction(func_b)) {
throw new TypeError('[aid.alt] Type of func_a, func_b parameters must be Function.');
}
return function(value) {
var result_a = func_a(value);
// undefined, null, false
if (!aid.isDefined(result_a) || result_a === false) return func_b(value);
return result_a;
};
};
/**
* seq combinator
*
* @static
* @method seq
* @param {Function} functions
* @returns {Function} return function
* @example
* var seq = aid.seq(function(val) { console.log(val); }, function(val) { console.log(val / -1) });
* seq(99); // 99, -99
*/
aid.seq = function seq(/* functions */) {
var funcs = _slice.call(arguments);
funcs.forEach(function(func) {
if (!aid.isFunction(func)) throw new TypeError('[aid.seq] Requires function parameters.');
});
return function(value) {
funcs.forEach(function(func) {
func.call(null, value);
});
};
};
/**
* fork(join) combinator
*
* @static
* @method fork
* @param {Function} join
* @param {Function} func_a
* @param {Function} func_b
* @returns {Function} return function
* @example
* var fork = aid.fork(function(val_a, val_b) { return [val_a, val_b]; }, function(val) { return val; }, function(val) { return !val; });
* console.log( fork(true) ); // [true, false]
*/
aid.fork = function fork(join, func_a, func_b) {
if (!aid.isFunction(join) || !aid.isFunction(func_a) || !aid.isFunction(func_b)) {
throw new TypeError('[aid.fork] Type of join, func_a, func_b parameters must be Function.');
}
return function(value) {
return join.call(null, func_a.call(null, value), func_b.call(null, value));
};
};
/*
* Data Structure
*/
// Stack
var Stack = function() {
this._dataStore = [];
this._top = 0;
};
Stack.prototype.push = function stack_push(element) {
if (this._top < 0) this._top = 0;
this._dataStore[this._top++] = element;
};
Stack.prototype.pop = function stack_pop() {
return this._dataStore[--this._top];
};
Stack.prototype.peek = function stack_peek() {
return this._dataStore[this._top - 1];
};
Stack.prototype.length = function stack_length() {
return this._top > 0 ? this._top : 0;
};
Stack.prototype.clear = function stack_clear() {
this._dataStore = [];
this._top = 0;
};
/**
* createStack
*
* @static
* @method createStack
* @returns {Stack} return stack instance
* @example
* var stack = aid.createStack(); // use push, pop, peek, length, clear methods
*/
aid.createStack = function createStack() {
return new Stack();
};
// Queue
var Queue = function() {
this._dataStore = [];
};
Queue.prototype.enqueue = function queue_enqueue(element) {
this._dataStore.push(element);
};
Queue.prototype.dequeue = function queue_dequeue() {
return this._dataStore.shift();
};
Queue.prototype.front = function queue_front() {
return this._dataStore[0];
};
Queue.prototype.rear = function queue_rear() {
return this._dataStore[this._dataStore.length - 1];
};
Queue.prototype.length = function queue_length() {
return this._dataStore.length;
};
Queue.prototype.isEmpty = function queue_isEmpty() {
if (this._dataStore.length <= 0) return true;
return false;
};
/**
* createQueue
*
* @static
* @method createQueue
* @returns {Queue} return queue instance
* @example
* var queue = aid.createQueue(); // use enqueue, dequeue, front, rear, length, isEmpty methods
*/
aid.createQueue = function createQueue() {
return new Queue();
};
// LinkedList node
var LinkedListNode = function(data) {
this.data = data;
this.next = null;
};
// LinkedList
var LinkedList = function() {
this.head = new LinkedListNode('LinkedListHead_' + Date.now());
};
LinkedList.prototype.getHead = function linkedList_getHead() {
return this.head;
};
LinkedList.prototype.isEmpty = function linkedList_isEmpty() {
return !this.head.next;
};
LinkedList.prototype.find = function linkedList_find(data) {
var node = this.head;
while (node.data !== data) {
node = node.next;
if (!node) return node;
}
return node;
};
LinkedList.prototype.findPrevious = function linkedList_findPrevious(data) {
if (this.head.data === data) return null;
var node = this.head;
while (node.next && node.next.data !== data) {
node = node.next;
}
return node;
};
LinkedList.prototype.insert = function linkedList_insert(data, prevNodeData) {
var prevNode = this.find(prevNodeData);
if (!prevNode) return false;
var insertNode = new LinkedListNode(data);
insertNode.next = prevNode.next;
prevNode.next = insertNode;
return true;
};
LinkedList.prototype.remove = function linkedList_remove(data) {
var prevNode = this.findPrevious(data);
if (prevNode && prevNode.next) {
prevNode.next = prevNode.next.next;
return true;
}
return false;
};
LinkedList.prototype.append = function linkedList_append(data) {
var appendNode = new LinkedListNode(data);
var node = this.head;
while (node.next) {
node = node.next;
}
node.next = appendNode;
};
LinkedList.prototype.getAllNodes = function linkedList_getAllNodes() {
var nodes = [this.head],
node = this.head;
while (node.next) {
nodes.push(node.next);
node = node.next;
}
return nodes;
};
/**
* createLinkedList
*
* @static
* @method createLinkedList
* @returns {LinkedList} return linkedList instance
* @example
* var linkedList = aid.createLinkedList(); // use getHead, isEmpty, find, findPrevious, insert, remove, append, getAllNodes methods
*/
aid.createLinkedList = function createLinkedList() {
return new LinkedList();
};
// Set
var _Set = function() {
this.store = [];
};
_Set.prototype.size = function set_size() {
return this.store.length;
};
_Set.prototype.add = function set_add(value) {
if (!this.has(value)) {
this.store.push(value);
return true;
}
return false;
};
_Set.prototype.remove = function set_remove(value) {
var pos = this.store.indexOf(value);
if (pos >= 0) {
this.store.splice(pos, 1);
return true;
}
return false;
};
_Set.prototype.clear = function set_clear() {
this.store = [];
};
_Set.prototype.has = function set_has(value) {
return this.store.indexOf(value) >= 0;
};
_Set.prototype.values = function set_values() {
return this.store;
};
// A ∪ B
_Set.prototype.union = function set_union(otherSet) {
var unionSet = new _Set();
this.values().forEach(function(val) {
unionSet.add(val);
});
otherSet.values().forEach(function(val) {
unionSet.add(val);
});
return unionSet;
};
// A ∩ B
_Set.prototype.intersection = function set_intersection(otherSet) {
var intersectionSet = new _Set();
this.values().forEach(function(val) {
if (otherSet.has(val)) intersectionSet.add(val);
});
return intersectionSet;
};
// A - B
_Set.prototype.difference = function set_difference(otherSet) {
var differenceSet = new _Set();
this.values().forEach(function(val) {
if (!otherSet.has(val)) differenceSet.add(val);
});
return differenceSet;
};
// A ⊆ B
_Set.prototype.isSubset = function set_subset(superset) {
if (this.size() > superset.size()) return false;
var isSupersetContainsSubset = this.values().every(function(val) {
return superset.has(val);
});
return isSupersetContainsSubset;
};
/**
* createSet
*
* @static
* @method createSet
* @returns {Set} return set instance
* @example
* var set = aid.createSet(); // use size, add, remove, clear, has, values, union, intersection, difference, isSubset methods
*/
aid.createSet = function createSet() {
return new _Set();
};
// HashTable
var HashTable = function(hashFunc) {
this.table = [];
this._hashFunc = aid.isFunction(hashFunc) ? hashFunc : this._djb2Hash;
};
HashTable.prototype.put = function hashTable_put(key, value) {
var position = this._hashFunc.call(null, key);
if (this.table[position] === undefined) this.table[position] = aid.createLinkedList();
var linkedList = this.table[position];
linkedList.append({ key: key, value: value });
};
HashTable.prototype.get = function hashTable_get(key) {
var position = this._hashFunc.call(null, key);
var linkedList = this.table[position];
if (linkedList) {
var head = linkedList.getHead();
if (!head.next) return undefined;
var node = head;
while (node && node.data && node.data.key !== key) {
node = node.next;
}
if (node && node.data) return node.data.value;
}
return undefined;
};
HashTable.prototype.remove = function hashTable_remove(key) {
var position = this._hashFunc.call(null, key);
var linkedList = this.table[position];
if (linkedList) {
var head = linkedList.getHead();
if (!head.next) return false;
// find previous node
var node = head;
while (node.next && node.next.data && node.next.data.key !== key) {
node = node.next;
}
// remove node
if (node && node.next) {
node.next = node.next.next;
if (linkedList.isEmpty()) this.table[position] = undefined;
return true;
}
}
return false;
};
HashTable.prototype._djb2Hash = function hashTable_djb2Hash(key) {
var hash = 5381;
for (var i = 0, max = key.length; i < max; i++) {
hash = hash * 33 + key.charCodeAt(i);
}
return hash % 1013;
};
/**
* createHashTable
*
* @static
* @method createQueue
* @returns {Queue} return hashTable instance that use djb2 hash function.
* @example
* var HashTable = aid.createHashTable(); // use put, get, remove methods
*/
aid.createHashTable = function createHashTable(hashFunc) {
return new HashTable(hashFunc);
};
// Dictionary
function Dictionary() {
this.items = {};
}
Dictionary.prototype.has = function(key) {
return _hasOwnProperty.call(this.items, key);
};
Dictionary.prototype.get = function(key) {
return this.has(key) ? this.items[key] : undefined;
};
Dictionary.prototype.set = function(key, value) {
this.items[key] = value;
};
Dictionary.prototype.remove = function(key) {
if (this.has(key)) {
delete this.items[key];
return true;
}
return false;
};
Dictionary.prototype.clear = function() {
this.items = {};
};
Dictionary.prototype.keys = function() {
return Object.keys(this.items);
};
Dictionary.prototype.values = function() {
var values = [];
for (var key in this.items) {
if (this.has(key)) values.push(this.items[key]);
}
return values;
};
Dictionary.prototype.size = function() {
return Object.keys(this.items).length;
};
Dictionary.prototype.getItems = function() {
return this.items;
};
/**
* createDictionary
*
* @static
* @method createDictionary
* @returns {Dictionary} return Dictionary instance
* @example
* var dictionary = aid.createDictionary(); // use has, get, set, remove, clear, keys, values, size, getItems methods
*/
aid.createDictionary = function createDictionary() {
return new Dictionary();
};
// BinarySearchTree node
var BinarySearchTreeNode = function(data) {
this.data = data;
this.left = null;
this.right = null;
};
// BinarySearchTree node
var BinarySearchTree = function() {
this.root = null;
};
BinarySearchTree.prototype._insertNode = function(node, newNode) {
if (!aid.isDefined(node) || !aid.isDefined(newNode))
throw new TypeError('[BinarySearchTree.prototype._insertNode] node and newNode parameters must be defined.');
// duplicated value is not allowed in this BinarySearchTree
if (node.data === newNode.data) return;
if (newNode.data < node.data) {
aid.isDefined(node.left) ? this._insertNode(node.left, newNode) : (node.left = newNode);
} else {
aid.isDefined(node.right) ? this._insertNode(node.right, newNode) : (node.right = newNode);
}
};
BinarySearchTree.prototype._searchNode = function(node, data) {
if (!aid.isDefined(node)) return false;
if (!aid.isDefined(data))
throw new TypeError('[BinarySearchTree.prototype._searchNode] data parameters must be defined.');
if (data < node.data) {
return this._searchNode(node.left, data);
} else if (data > node.data) {
return this._searchNode(node.right, data);
} else {
return true;
}
};
BinarySearchTree.prototype._findMinNode = function(node) {
if (!aid.isDefined(node)) return null;
while (node && aid.isDefined(node.left)) {
node = node.left;
}
return node;
};
BinarySearchTree.prototype._removeNode = function(node, data) {
if (!aid.isDefined(data))
throw new TypeError('[BinarySearchTree.prototype._removeNode] data parameters must be defined.');
if (!aid.isDefined(node)) return null;
if (data < node.data) {
node.left = this._removeNode(node.left, data);
return node;
} else if (data > node.data) {
node.right = this._removeNode(node.right, data);
return node;
} else {
// case: remove leaf node
if (!aid.isDefined(node.left) && !aid.isDefined(node.right)) {
node = null;
return node;
}
// case: remove node has one child node
if (!aid.isDefined(node.left)) {
node = node.right;
return node;
} else if (!aid.isDefined(node.right)) {
node = node.left;
return node;
}
// case: remove node has two children nodes
var aux = this._findMinNode(node.right);
node.data = aux.data;
node.right = this._removeNode(node.right, aux.data);
return node;
}
};
BinarySearchTree.prototype._minNode = function(node) {
if (!aid.isDefined(node)) return null;
while (node && aid.isDefined(node.left)) node = node.left;
return node.data;
};
BinarySearchTree.prototype._maxNode = function(node) {
if (!aid.isDefined(node)) return null;
while (node && aid.isDefined(node.right)) node = node.right;
return node.data;
};
BinarySearchTree.prototype._inOrderTraverseNode = function(node, callback) {
if (!aid.isFunction(callback))
throw new TypeError(
'[BinarySearchTree.prototype._inOrderTraverseNode] Type of callback parameter must be Function.'
);
// visit node.left, node.data, node.right
if (aid.isDefined(node)) {
this._inOrderTraverseNode(node.left, callback);
callback(node.data);
this._inOrderTraverseNode(node.right, callback);
}
};
BinarySearchTree.prototype._preOrderTraverseNode = function(node, callback) {
if (!aid.isFunction(callback))
throw new TypeError(
'[BinarySearchTree.prototype._preOrderTraverseNode] Type of callback parameter must be Function.'
);
// visit node.data, node.left, node.right
if (aid.isDefined(node)) {
callback(node.data);
this._preOrderTraverseNode(node.left, callback);
this._preOrderTraverseNode(node.right, callback);
}
};
BinarySearchTree.prototype._postOrderTraverseNode = function(node, callback) {
if (!aid.isFunction(callback))
throw new TypeError(
'[BinarySearchTree.prototype._postOrderTraverseNode] Type of callback parameter must be Function.'
);
// visit node.left, node.right, node.data
if (aid.isDefined(node)) {
this._postOrderTraverseNode(node.left, callback);
this._postOrderTraverseNode(node.right, callback);
callback(node.data);
}
};
BinarySearchTree.prototype.getRoot = function binarySearchTree_getRoot() {
return this.root;
};
BinarySearchTree.prototype.insert = function binarySearchTree_insert(data) {
if (!aid.isDefined(data))
throw new TypeError('[BinarySearchTree.prototype.insert] data parameter must be defined.');
var newNode = new BinarySearchTreeNode(data);
if (aid.isDefined(this.root)) {
this._insertNode(this.root, newNode);
} else {
this.root = newNode;
}
};
BinarySearchTree.prototype.search = function binarySearchTree_search(data) {
if (!aid.isDefined(data))
throw new TypeError('[BinarySearchTree.prototype.search] data parameter must be defined.');
return this._searchNode(this.root, data);
};
BinarySearchTree.prototype.remove = function binarySearchTree_remove(data) {
if (!aid.isDefined(data))
throw new TypeError('[BinarySearchTree.prototype.remove] data parameter must be defined.');
this.root = this._removeNode(this.root, data);
};
BinarySearchTree.prototype.min = function binarySearchTree_min() {
return this._minNode(this.root);
};
BinarySearchTree.prototype.max = function binarySearchTree_max() {
return this._maxNode(this.root);
};
BinarySearchTree.prototype.inOrderTraverse = function binarySearchTree_inOrderTraverse(callback) {
if (!aid.isFunction(callback))
throw new TypeError('[BinarySearchTree.prototype.inOrderTraverse] Type of callback parameter must be Function.');
this._inOrderTraverseNode(this.root, callback);
};
BinarySearchTree.prototype.preOrderTraverse = function binarySearchTree_preOrderTraverse(callback) {
if (!aid.isFunction(callback))
throw new TypeError('[BinarySearchTree.prototype.preOrderTraverse] Type of callback parameter must be Function.');
this._preOrderTraverseNode(this.root, callback);
};
BinarySearchTree.prototype.postOrderTraverse = function binarySearchTree_postOrderTraverse(callback) {
if (!aid.isFunction(callback))
throw new TypeError(
'[BinarySearchTree.prototype.postOrderTraverse] Type of callback parameter must be Function.'
);
this._postOrderTraverseNode(this.root, callback);
};
/**
* createBinarySearchTree
*
* @static
* @method createBinarySearchTree
* @returns {BinarySearchTree} return BinarySearchTree instance
* @example
* var binarySearchTree = aid.createBinarySearchTree(); // use getRoot, insert, search, remove, min, max, inOrderTraverse, preOrderTraverse, postOrderTraverse methods
*/
aid.createBinarySearchTree = function createBinarySearchTree() {
return new BinarySearchTree();
};
// Graph
function Graph() {
this.vertices = [];
this.adjacencyList = aid.createDictionary();
}
Graph.prototype.addVertex = function(vertex) {
if (array.indexOf(this.vertices, vertex) >= 0)
throw new Error('[Graph.prototype.addVertex] this.vertices already has the same vertex.');
this.vertices.push(vertex);
this.adjacencyList.set(vertex, []);
};
Graph.prototype.addEdge = function(fromVertex, toVertex) {
if (array.indexOf(this.vertices, fromVertex) < 0)
throw new Error('[Graph.prototype.addEdge] this.vertices has not fromVertex.');
if (array.indexOf(this.vertices, toVertex) < 0)
throw new Error('[Graph.prototype.addEdge] this.vertices has not toVertex.');
this.adjacencyList.get(fromVertex).push(toVertex);
this.adjacencyList.get(toVertex).push(fromVertex);
};
Graph.prototype.bfs = function(fromVertex, callback) {
// breadth-first serach
if (array.indexOf(this.vertices, fromVertex) < 0)
throw new Error('[Graph.prototype.bfs] this.vertices has not fromVertex.');
var neighbors = this.adjacencyList.get(fromVertex);
if (!neighbors || neighbors.length <= 0)
throw new Error('[Graph.prototype.bfs] fromVertex is not connected to any vertices.');
if (aid.isDefined(callback) && !aid.isFunction(callback))
throw new TypeError('[Graph.prototype.bfs] Type of callback parameter must be undefined or null or Function.');
// colors has 3 types of vertex color
// 'white' : vertex is not visited
// 'grey' : vertex is visited but hasn't been explored