'use strict'; import * as OxCore from './Core.js'; import * as OxFunction from './Function.js'; import * as OxConstants from './Constants.js'; import * as OxType from './Type.js'; import * as OxObject from './Object.js'; const Ox = {}; Object.assign(Ox, OxCore, OxConstants, OxType, OxObject, OxFunction, ); /*@ Ox.avg Returns the average of an array's values, or an object's properties (collection) -> Average value collection <[n]|o> Array or object with numerical values > Ox.avg([-1, 0, 1]) 0 > Ox.avg({a: 1, b: 2, c: 3}) 2 > Ox.avg('avg is 0.1') 0.1 @*/ export function avg(collection) { return Ox.sum(collection) / len(collection); }; /*@ Ox.clone Returns a (shallow or deep) copy of an array or object > (function() { var a = ['v'], b = Ox.clone(a); a[0] = null; return b[0]; }()) 'v' > (function() { var a = {k: 'v'}, b = Ox.clone(a); a.k = null; return b.k; }()) 'v' > Ox.clone(0) 0 > (function() { var a = [[0, 1]], b = Ox.clone(a); a[0][0] = null; return b[0]; }()) [null, 1] > (function() { var a = [[0, 1]], b = Ox.clone(a, true); a[0][0] = null; return b[0]; }()) [0, 1] @*/ export function clone(collection, deep) { var ret, type = Ox.typeOf(collection); if (type != 'array' && type != 'object') { ret = collection; } else if (deep) { ret = type == 'array' ? [] : {}; forEach(collection, function(value, key) { type = Ox.typeOf(value); ret[key] = type == 'array' || type == 'object' ? Ox.clone(value, true) : value; }); } else { ret = type == 'array' ? collection.slice() : Ox.extend({}, collection); } return ret; }; /*@ Ox.contains Tests if a collection contains a value (collection, value) -> If true, the collection contains the value collection Collection value <*> Any value > Ox.contains(['foo', 'bar'], 'foo') true > Ox.contains({foo: 'bar'}, 'bar') true > Ox.contains({foo: 'bar'}, 'foo') false > Ox.contains('foobar', 'bar') true @*/ export function contains(collection, value) { var type = Ox.typeOf(collection); return ( type == 'nodelist' || type == 'object' ? Ox.values(collection) : collection ).indexOf(value) > -1; }; /*@ Ox.count Counts the occurences of values in a collection (collection) -> Number of occurrences per value (collection, value) -> Number of occurrences of the given value collection Collection value <*> Any value > Ox.count(['f', 'o', 'o']) {f: 1, o: 2} > Ox.count({a: 'f', b: 'o', c: 'o'}) {f: 1, o: 2} > Ox.count('foo') {f: 1, o: 2} > Ox.count('foo', 'f') 1 > Ox.count('foo', 'x') 0 @*/ export function count(collection, value) { var count = {}; forEach(collection, function(value) { count[value] = (count[value] || 0) + 1; }); return value ? count[value] || 0 : count; }; /*@ Ox.every Tests if every element of a collection satisfies a given condition Unlike `Array.prototype.every`, `Ox.every` works for arrays, objects and strings. (collection[, iterator]) -> True if every element passes the test collection Collection iterator Iterator value <*> Value key Index or key collection The collection > Ox.every([0, 1, 2], function(v, i) { return v == i; }) true > Ox.every({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) false > Ox.every('foo', function(v) { return v == 'f'; }) false > Ox.every([true, true, true]) true @*/ export function every(collection, iterator, that) { iterator = iterator || Ox.identity; return forEach(collection, function(value, key, collection) { return !!iterator.call(that, value, key, collection); }) == len(collection); }; /*@ Ox.forEach forEach loop `Ox.forEach` loops over arrays, objects and strings. Returning `false` from the iterator acts like a `break` statement. Unlike `for`, which leaks its counter variable to the outer scope, `Ox.forEach` returns it. (collection, iterator[, that]) -> Next index collection Collection iterator Iterator value <*> Value key Index or key collection The collection that The iterator's `this` binding > Ox.test.string "012abcfoo" > Ox.forEach({a: 'f', b: 'o', c: 'o'}, function(v, k) { return v != 'o' }); 1 @*/ export function forEach(collection, iterator, that) { var i = 0, key, type = Ox.typeOf(collection); if (type == 'object' || type == 'storage') { for (key in collection) { if ( Ox.hasOwn(collection, key) && iterator.call(that, collection[key], key, collection) === false ) { break; } i++; } } else { collection = Ox.slice(collection); for (i = 0; i < collection.length; i++) { if ( i in collection && iterator.call(that, collection[i], i, collection) === false ) { break; } } } return i; }; /*@ Ox.indicesOf Returns all indices of collection elements that pass a test > Ox.indicesOf([1, 2, 3], function(val) { return val % 2 == 1; }) [0, 2] > Ox.indicesOf({a: 1, b: 2, c: 3}, function(val) { return val % 2 == 1; }) ['a', 'c'] > Ox.indicesOf('FooBar', function(val) { return val == val.toUpperCase(); }) [0, 3] > Ox.indicesOf([1, 2, 3], function(val) { return val == 0; }) [] @*/ export function indicesOf(collection, test) { var ret = []; forEach(collection, function(value, index) { test(value) && ret.push(index); }); return ret; }; /*@ Ox.len Returns the length of an array, nodelist, object, storage or string Not to be confused with `Ox.length`, which is the `length` property of the `Ox` function (`1`). > Ox.len((function() { return arguments; }(1, 2, 3))) 3 > Ox.len([1, 2, 3]) 3 > Ox.len([,]) 1 > Ox.typeOf(Ox.len(document.getElementsByTagName('a'))) 'number' > Ox.len({a: 1, b: 2, c: 3}) 3 > Ox.typeOf(Ox.len(localStorage)) 'number' > Ox.len('abc') 3 > Ox.len(function(a, b, c) {}) undefined @*/ export function len(collection) { var ret, type = Ox.typeOf(collection); if ( type == 'arguments' || type == 'array' || type == 'nodelist' || type == 'string' ) { ret = collection.length; } else if (type == 'object' || type == 'storage') { ret = Object.keys(collection).length; } return ret; }; /*@ Ox.map Transforms the values of an array, object or string Unlike `Array.prototype.map`, `Ox.map` works for arrays, objects and strings. > Ox.map([2, 1, 0], function(v, i) { return v == i; }) [false, true, false] > Ox.map({a: 'b', b: 'b', c: 'b'}, function(v, k) { return v == k; }) {a: false, b: true, c: false} > Ox.map('foo', function(v) { return v.toUpperCase(); }) 'FOO' > Ox.map([,], function(v, i) { return i; }) [,] @*/ export function map(collection, iterator, that) { var ret, type = Ox.typeOf(collection); if (type == 'object' || type == 'storage') { ret = {}; forEach(collection, function(value, key) { ret[key] = iterator.call(that, value, key, collection); }); } else { ret = Ox.slice(collection).map(iterator); if (type == 'string') { ret = ret.join(''); } } return ret; }; /*@ Ox.max Returns the maximum value of a collection > Ox.max([1, 2, 3]) 3 > Ox.max({a: 1, b: 2, c: 3}) 3 > Ox.max('123') 3 > Ox.max([]) -Infinity @*/ export function max(collection) { var ret, values = Ox.values(collection); if (values.length < Ox.STACK_LENGTH) { ret = Math.max.apply(null, values); } else { ret = values.reduce(function(previousValue, currentValue) { return Math.max(previousValue, currentValue); }, -Infinity); } return ret; }; /*@ Ox.min Returns the minimum value of a collection > Ox.min([1, 2, 3]) 1 > Ox.min({a: 1, b: 2, c: 3}) 1 > Ox.min('123') 1 > Ox.min([]) Infinity @*/ export function min(collection) { var ret, values = Ox.values(collection); if (values.length < Ox.STACK_LENGTH) { ret = Math.min.apply(null, values); } else { ret = values.reduce(function(previousValue, currentValue) { return Math.min(previousValue, currentValue); }, Infinity); } return ret; }; /*@ Ox.numberOf Returns the number of elements in a collection that pass a test (collection, test) -> Number of elements collection Collection test Test function value <*> Value key Key collection Collection > Ox.numberOf([0, 1, 0, 1], function(v) { return v; }) 2 > Ox.numberOf({a: 'a', b: 'c'}, function(v, k) { return v == k; }) 1 > Ox.numberOf('foo', function(v, k, c) { return v == c[k - 1]; }) 1 @*/ export function numberOf(collection, test) { return len(Ox.filter(collection, test)); }; /*@ Ox.remove Removes an element from an array or object and returns it (collection, element) -> <*> Element, or undefined if not found > Ox.remove(Ox.test.collection[0], 'b') 'b' > Ox.remove(Ox.test.collection[1], 1) 1 > Ox.remove(Ox.test.collection[1], 3) void 0 > Ox.test.collection [['a', 'c'], {a: 0, c: 2}] @*/ export function remove(collection, element) { var ret, key; if (Ox.isArray(collection)) { key = collection.indexOf(element); if (key > -1) { ret = collection.splice(key, 1)[0]; } } else { key = Ox.keyOf(collection, element); if (key) { ret = collection[key]; delete collection[key]; } } return ret; }; /*@ Ox.reverse Reverses an array or string > Ox.reverse([1, 2, 3]) [3, 2, 1] > Ox.reverse('foobar') 'raboof' @*/ export function reverse(collection) { return Ox.isArray(collection) ? Ox.clone(collection).reverse() : collection.toString().split('').reverse().join(''); }; /*@ Ox.shuffle Randomizes the order of values within a collection > Ox.shuffle([1, 2, 3]).length 3 > Ox.len(Ox.shuffle({a: 1, b: 2, c: 3})) 3 > Ox.shuffle('123').split('').sort().join('') '123' @*/ export function shuffle(collection) { var keys, ret, type = Ox.typeOf(collection), values; if (type == 'object' || type == 'storage') { keys = Object.keys(collection); values = Ox.shuffle(Ox.values(collection)); ret = {}; keys.forEach(function(key, index) { ret[key] = values[index]; }); } else { ret = []; Ox.slice(collection).forEach(function(value, index) { var random = Math.floor(Math.random() * (index + 1)); ret[index] = ret[random]; ret[random] = value; }); if (type == 'string') { ret = ret.join(''); } } return ret; }; /*@ Ox.some Tests if one or more elements of a collection meet a given condition Unlike `Array.prototype.some`, `Ox.some` works for arrays, objects and strings. > Ox.some([2, 1, 0], function(i, v) { return i == v; }) true > Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) true > Ox.some("foo", function(v) { return v == 'f'; }) true > Ox.some([false, null, 0, '', void 0]) false @*/ export function some(collection, iterator, that) { iterator = iterator || Ox.identity; return forEach(collection, function(value, key, collection) { return !iterator.call(that, value, key, collection); }) < len(collection); }; /*@ Ox.sum Returns the sum of the values of a collection > Ox.sum(1, 2, 3) 6 > Ox.sum([1, 2, 3]) 6 > Ox.sum({a: 1, b: 2, c: 3}) 6 > Ox.sum('123') 6 > Ox.sum('123foo') 6 > Ox.sum('08', -2, 'foo') 6 @*/ export function sum(collection) { var ret = 0; collection = arguments.length > 1 ? Ox.slice(arguments) : collection; forEach(collection, function(value) { value = +value; ret += isFinite(value) ? value : 0; }); return ret; }; /* FIXME: do we need this kind of zip functionality? export function arrayToObject(array, key) { var ret = {}; array.forEach(function(v) { ret[v[key]] = v; }); return ret; }; export function objectToArray(object, key) { var ret = []; forEach(object, function(v, k) { ret.push(Ox.extend(v, key, k)); }); return ret; }; */ /*@ Ox.values Returns the values of a collection (collection) -> Array of values collection Collection > Ox.values([1, 2, 3]) [1, 2, 3] > Ox.values({a: 1, b: 2, c: 3}) [1, 2, 3] > Ox.typeOf(Ox.values(localStorage)) 'array' > Ox.values('abc') ['a', 'b', 'c'] > Ox.values([1,,3]) [1,,3] @*/ export function values(collection) { var ret, type = Ox.typeOf(collection); if (type == 'array' || type == 'nodelist') { ret = Ox.slice(collection); } else if (type == 'object' || type == 'storage') { ret = []; forEach(collection, function(value) { ret.push(value); }); } else if (type == 'string') { ret = collection.split(''); } return ret; }; /*@ Ox.walk Iterates over a nested data structure (collection, iterator[, that]) -> undefined collection Collection iterator Iterator value <*> Value keys Array of keys collection The collection that The iterator's `this` binding > Ox.test.number 6 > Ox.test.array [['a'], ['b', 'c'], ['b', 'd']] @*/ export function walk(collection, iterator, that, keys) { keys = keys || []; forEach(collection, function(value, key) { var keys_ = keys.concat(key); iterator.call(that, value, keys_, collection); Ox.walk(collection[key], iterator, that, keys_); }); };