lively.lang
Version:
JavaScript utils providing useful abstractions for working with collections, functions, objects.
1,193 lines (1,055 loc) • 35 kB
JavaScript
/*global System, global*/
/*
* Methods to make working with arrays more convenient and collection-like
* abstractions for groups, intervals, grids.
*/
import { equals as objectEquals } from "./object.js";
import { once, Null as NullFunction } from "./function.js";
import Group from "./Group.js";
var GLOBAL = typeof System !== "undefined" ? System.global :
(typeof window !== 'undefined' ? window : global);
var features = {
from: !!Array.from,
filter: !!Array.prototype.filter,
find: !!Array.prototype.find,
findIndex: !!Array.prototype.findIndex,
includes: !!Array.prototype.includes
}
// variety of functions for Arrays
// -=-=-=-=-=-=-=-
// array creations
// -=-=-=-=-=-=-=-
function range(begin, end, step) {
// Examples:
// arr.range(0,5) // => [0,1,2,3,4,5]
// arr.range(0,10,2) // => [0,2,4,6,8,10]
step = step || 0;
var result = [];
if (begin <= end) {
if (step <= 0) step = -step || 1;
for (var i = begin; i <= end; i += step) result.push(i);
} else {
if (step >= 0) step = -step || -1;
for (var i = begin; i >= end; i += step) result.push(i);
}
return result;
}
var from = features.from ? Array.from : function(iterable) {
// Makes JS arrays out of array like objects like `arguments` or DOM `childNodes`
if (!iterable) return [];
if (Array.isArray(iterable)) return iterable;
if (iterable.toArray) return iterable.toArray();
var length = iterable.length,
results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
function withN(n, obj) {
// Example:
// arr.withN(3, "Hello") // => ["Hello","Hello","Hello"]
var result = new Array(n);
while (n > 0) result[--n] = obj;
return result;
}
function genN(n, generator) {
// Number -> Function -> Array
// Takes a generator function that is called for each `n`.
// Example:
// arr.genN(3, num.random) // => [46,77,95]
var result = new Array(n);
while (n > 0) result[--n] = generator(n);
return result;
}
// -=-=-=-=-
// filtering
// -=-=-=-=-
function filter(array, iterator, context) {
// [a] -> (a -> Boolean) -> c? -> [a]
// Calls `iterator` for each element in `array` and returns a subset of it
// including the elements for which `iterator` returned a truthy value.
// Like `Array.prototype.filter`.
return array.filter(iterator, context);
}
var detect = features.find ?
function(arr, iterator, context) { return arr.find(iterator, context); } :
function(arr, iterator, context) {
// [a] -> (a -> Boolean) -> c? -> a
// returns the first occurrence of an element in `arr` for which iterator
// returns a truthy value
for (var value, i = 0, len = arr.length; i < len; i++) {
value = arr[i];
if (iterator.call(context, value, i)) return value;
}
return undefined;
}
var findIndex = features.findIndex ?
function(arr, iterator, context) { return arr.findIndex(iterator, context); } :
function(arr, iterator, context) {
var i = -1;
return arr.find(function(ea, j) { i = j; return iterator.call(ea, context); }) ? i : -1;
}
function findAndGet(arr, iterator) {
// find the first occurence for which `iterator` returns a truthy value and
// return *this* value, i.e. unlike find the iterator result and not the
// element of the list is returned
var result;
arr.find(function(ea, i) { return result = iterator(ea, i); });
return result;
}
function filterByKey(arr, key) {
// [a] -> String -> [a]
// Example:
// var objects = [{x: 3}, {y: 4}, {x:5}]
// arr.filterByKey(objects, "x") // => [{x: 3},{x: 5}]
return arr.filter(function(ea) { return !!ea[key]; });
}
function grep(arr, filter, context) {
// [a] -> String|RegExp -> [a]
// `filter` can be a String or RegExp. Will stringify each element in
// Example:
// ["Hello", "World", "Lively", "User"].grep("l") // => ["Hello","World","Lively"]
if (typeof filter === 'string') filter = new RegExp(filter, 'i');
return arr.filter(filter.test.bind(filter))
}
function mask(array, mask) {
// select every element in array for which array's element is truthy
// Example: [1,2,3].mask([false, true, false]) => [2]
return array.filter(function(_, i) { return !!mask[i]; });
}
function reject(array, func, context) {
// show-in-doc
function iterator(val, i) { return !func.call(context, val, i); }
return array.filter(iterator);
}
function rejectByKey(array, key) {
// show-in-doc
return array.filter(function(ea) { return !ea[key]; });
}
function without(array, elem) {
// non-mutating
// Example:
// arr.without([1,2,3,4,5,6], 3) // => [1,2,4,5,6]
return array.filter(val => val !== elem);
}
function withoutAll(array, otherArr) {
// non-mutating
// Example:
// arr.withoutAll([1,2,3,4,5,6], [3,4]) // => [1,2,5,6]
return array.filter(val => otherArr.indexOf(val) === -1);
}
function uniq(array, sorted) {
// non-mutating
// Removes duplicates from array.
// if sorted == true then assume array is sorted which allows uniq to be more
// efficient
// uniq([3,5,6,2,3,4,2,6,4])
if (!array.length) return array;
let result = [array[0]];
if (sorted) {
for (let i = 1; i < array.length; i++) {
let val = array[i];
if (val !== result[result.length])
result.push(val);
}
} else {
for (let i = 1; i < array.length; i++) {
let val = array[i];
if (result.indexOf(val) === -1)
result.push(val);
}
}
return result;
}
function uniqBy(array, comparator, context) {
// like `arr.uniq` but with custom equality: `comparator(a,b)` returns
// BOOL. True if a and be should be regarded equal, false otherwise.
var result = array.slice();
for (let i = result.length; i--;) {
var item = array[i];
for (var j = i+1; j < result.length; j++) {
if (comparator.call(context, item, result[j]))
result.splice(j--, 1)
}
}
return result;
}
function uniqByKey(array, key) {
// like `arr.uniq` but with equality based on item[key]
let seen = {}, result = [];
for (var i = 0; i < array.length; i++) {
let item = array[i];
if (!seen[item[key]]) {
seen[item[key]] = true;
result.push(item);
}
}
return result;
}
function compact(array) {
// removes falsy values
// Example:
// arr.compact([1,2,undefined,4,0]) // => [1,2,4]
return array.filter(Boolean);
}
function mutableCompact(array) {
// fix gaps that were created with 'delete'
var i = 0, j = 0, len = array.length;
while (i < len) {
if (array.hasOwnProperty(i)) array[j++] = array[i];
i++;
}
while (j++ < len) array.pop();
return array;
}
// -=-=-=-=-
// iteration
// -=-=-=-=-
function forEach(array, iterator, context) {
// [a] -> (a -> Undefined) -> c? -> Undefined
// `iterator` is called on each element in `array` for side effects. Like
// `Array.prototype.forEach`.
return array.forEach(iterator, context);
}
function zip(/*arr, arr2, arr3*/) {
// Takes any number of lists as arguments. Combines them elment-wise.
// Example:
// arr.zip([1,2,3], ["a", "b", "c"], ["A", "B"])
// // => [[1,"a","A"],[2,"b","B"],[3,"c",undefined]]
var args = Array.from(arguments),
array = args.shift(),
iterator = typeof last(args) === 'function' ?
args.pop() : function(x) { return x; },
collections = [array].concat(args).map(function(ea) { return Array.from(ea); });
return array.map(function(value, index) {
return iterator(pluck(collections, index), index); });
}
function flatten(array, optDepth) {
// Turns a nested collection into a flat one.
// Example:
// arr.flatten([1, [2, [3,4,5], [6]], 7,8])
// // => [1,2,3,4,5,6,7,8]
if (typeof optDepth === "number") {
if (optDepth <= 0) return array;
optDepth--;
}
return array.reduce(function(flattened, value) {
return flattened.concat(Array.isArray(value) ?
flatten(value, optDepth) : [value]);
}, []);
}
function flatmap(array, it, ctx) {
// the simple version
// Array.prototype.concat.apply([], array.map(it, ctx));
// causes stack overflows with really big arrays
var results = [];
for (var i = 0; i < array.length; i++) {
results.push.apply(results, it.call(ctx, array[i], i));
}
return results;
}
function interpose(array, delim) {
// Injects delim between elements of array
// Example:
// lively.lang.arr.interpose(["test", "abc", 444], "aha"));
// // => ["test","aha","abc","aha",444]
return array.reduce(function(xs, x) {
if (xs.length > 0) xs.push(delim)
xs.push(x); return xs;
}, []);
}
function delimWith(array, delim) {
// ignore-in-doc
// previously used, use interpose now!
return interpose(array, delim);
}
// -=-=-=-=-
// mapping
// -=-=-=-=-
function map(array, iterator, context) {
// [a] -> (a -> b) -> c? -> [b]
// Applies `iterator` to each element of `array` and returns a new Array
// with the results of those calls. Like `Array.prototype.some`.
return array.map(iterator, context);
}
function invoke(array, method, arg1, arg2, arg3, arg4, arg5, arg6) {
// Calls `method` on each element in `array`, passing all arguments. Often
// a handy way to avoid verbose `map` calls.
// Example: arr.invoke(["hello", "world"], "toUpperCase") // => ["HELLO","WORLD"]
return array.map(function(ea) {
return ea[method](arg1, arg2, arg3, arg4, arg5, arg6);
});
}
function pluck(array, property) {
// Returns `property` or undefined from each element of array. For quick
// `map`s and similar to `invoke`.
// Example: arr.pluck(["hello", "world"], 0) // => ["h","w"]
return array.map(ea => ea[property]);
}
// -=-=-=-=-
// folding
// -=-=-=-=-
function reduce(array, iterator, memo, context) {
// Array -> Function -> Object? -> Object? -> Object?
// Applies `iterator` to each element of `array` and returns a new Array
// with the results of those calls. Like `Array.prototype.some`.
return array.reduce(iterator, memo, context);
}
function reduceRight(array, iterator, memo, context) {
// show-in-doc
return array.reduceRight(iterator, memo, context);
}
// -=-=-=-=-
// testing
// -=-=-=-=-
var isArray = Array.isArray;
var includes = features.includes ?
function(array, object) { return array.includes(object); } :
function(array, object) {
// Example: arr.include([1,2,3], 2) // => true
return array.indexOf(object) !== -1;
}
var include = includes;
function some(array, iterator, context) {
// [a] -> (a -> Boolean) -> c? -> Boolean
// Returns true if there is at least one abject in `array` for which
// `iterator` returns a truthy result. Like `Array.prototype.some`.
return array.some(iterator, context);
}
function every(array, iterator, context) {
// [a] -> (a -> Boolean) -> c? -> Boolean
// Returns true if for all abjects in `array` `iterator` returns a truthy
// result. Like `Array.prototype.every`.
return array.every(iterator, context);
}
function equals(array, otherArray) {
// Returns true iff each element in `array` is equal (`==`) to its
// corresponding element in `otherArray`
var len = array.length;
if (!otherArray || len !== otherArray.length) return false;
for (var i = 0; i < len; i++) {
if (array[i] && otherArray[i] && array[i].equals && otherArray[i].equals) {
if (!array[i].equals(otherArray[i])) {
return false;
} else {
continue;
}
}
if (array[i] != otherArray[i]) return false;
}
return true;
}
function deepEquals(array, otherArray) {
// Returns true iff each element in `array` is structurally equal
// (`lang.obj.equals`) to its corresponding element in `otherArray`
var len = array.length;
if (!otherArray || len !== otherArray.length) return false;
for (var i = 0; i < len; i++) {
if (!objectEquals(array[i], otherArray[i])) return false;
}
return true;
}
// -=-=-=-=-
// sorting
// -=-=-=-=-
function isSorted(array, descending) {
if (descending) {
for (var i = 1; i < array.length; i++)
if (array[i-1] < array[i]) return false;
} else {
for (var i = 1; i < array.length; i++)
if (array[i-1] > array[i]) return false;
}
return true;
}
function sort(array, sortFunc) {
// [a] -> (a -> Number)? -> [a]
// Just `Array.prototype.sort`
return array.sort(sortFunc);
}
function sortBy(array, iterator, context) {
// Example:
// arr.sortBy(["Hello", "Lively", "User"], function(ea) {
// return ea.charCodeAt(ea.length-1); }) // => ["Hello","User","Lively"]
return pluck(
array.map(function(value, index) {
return {value: value,criteria: iterator.call(context, value, index)};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
}
function sortByKey(array, key) {
// Example:
// lively.lang.arr.sortByKey([{x: 3}, {x: 2}, {x: 8}], "x")
// // => [{x: 2},{x: 3},{x: 8}]
return sortBy(array, ea => ea[key]);
}
function reverse(array) { return array.reverse(); }
function reversed(array) { return array.slice().reverse(); }
// -=-=-=-=-=-=-=-=-=-=-=-=-
// RegExp / String matching
// -=-=-=-=-=-=-=-=-=-=-=-=-
function reMatches(arr, re, stringifier) {
// result might include null items if re did not match (usful for masking)
// Example:
// var morphs = $world.withAllSubmorphsDo(function(x) { return x; ;
// morphs.mask(morphs.reMatches(/code/i))
stringifier = stringifier || String
return arr.map(ea => stringifier(ea).match(re));
}
// -=-=-=-=-=-
// accessors
// -=-=-=-=-=-
function first(array) { return array[0]; }
function last(array) { return array[array.length - 1]; }
// -=-=-=-=-=-=-=-
// Set operations
// -=-=-=-=-=-=-=-
function intersect(array1, array2) {
// set-like intersection
return uniq(array1).filter(item => array2.indexOf(item) > -1);
}
function union(array1, array2) {
// set-like union
var result = array1.slice();
for (var i = 0; i < array2.length; i++) {
var item = array2[i];
if (result.indexOf(item) === -1) result.push(item);
}
return result;
}
function pushAt(array, item, index) {
// inserts `item` at `index`, mutating
array.splice(index, 0, item);
}
function removeAt(array, index) {
// inserts item at `index`, mutating
array.splice(index, 1);
}
function remove(array, item) {
// removes first occurrence of item in `array`, mutating
var index = array.indexOf(item);
if (index >= 0) removeAt(array, index);
return item;
}
function pushAll(array, items) {
// appends all `items`, mutating
array.push.apply(array, items);
return array;
}
function pushAllAt(array, items, idx) {
// inserts all `items` at `idx`, mutating
array.splice.apply(array, [idx, 0].concat(items))
}
function pushIfNotIncluded(array, item) {
// only appends `item` if its not already in `array`, mutating
if (!array.includes(item)) array.push(item);
}
function replaceAt(array, item, index) {
// mutating
array.splice(index, 1, item);
}
function clear(array) {
// removes all items, mutating
array.length = 0; return array;
}
function isSubset(list1, list2) {
// are all elements in list1 in list2?
for (var i = 0; i < list1.length; i++)
if (!list2.includes(list1[i]))
return false;
return true;
}
// -=-=-=-=-=-=-=-=-=-=-=-
// asynchronous iteration
// -=-=-=-=-=-=-=-=-=-=-=-
function doAndContinue(array, iterator, endFunc, context) {
// Iterates over array but instead of consecutively calling iterator,
// iterator gets passed in the invocation for the next iteration step
// as a function as first parameter. This allows to wait arbitrarily
// between operation steps, great for managing dependencies between tasks.
// Related is [`fun.composeAsync`]().
// Example:
// arr.doAndContinue([1,2,3,4], function(next, n) {
// alert("At " + n);
// setTimeout(next, 100);
// }, function() { alert("Done"); })
// // If the elements are functions you can leave out the iterator:
// arr.doAndContinue([
// function(next) { alert("At " + 1); next(); },
// function(next) { alert("At " + 2); next(); }
// ], null, function() { alert("Done"); });
endFunc = endFunc || NullFunction;
context = context || GLOBAL;
iterator = iterator || function(next, ea, idx) { ea.call(context, next, idx); };
return array.reduceRight(function(nextFunc, ea, idx) {
return function() { iterator.call(context, nextFunc, ea, idx); }
}, endFunc)();
}
function nestedDelay(array, iterator, waitSecs, endFunc, context, optSynchronChunks) {
// Calls `iterator` for every element in `array` and waits between iterator
// calls `waitSecs`. Eventually `endFunc` is called. When passing a number n
// as `optSynchronChunks`, only every nth iteration is delayed.
endFunc = endFunc || function() {};
return array.clone().reverse().reduce(function(nextFunc, ea, idx) {
return function() {
iterator.call(context || GLOBAL, ea, idx);
// only really delay every n'th call optionally
if (optSynchronChunks && (idx % optSynchronChunks !== 0)) {
nextFunc()
} else {
nextFunc.delay(waitSecs);
}
}
}, endFunc)();
}
function forEachShowingProgress(/*array, progressBar, iterator, labelFunc, whenDoneFunc, context or spec*/) {
// ignore-in-doc
var args = Array.from(arguments),
array = args.shift(),
steps = array.length,
progressBar, iterator, labelFunc, whenDoneFunc, context,
progressBarAdded = false;
// init args
if (args.length === 1) {
progressBar = args[0].progressBar;
iterator = args[0].iterator;
labelFunc = args[0].labelFunction;
whenDoneFunc = args[0].whenDone;
context = args[0].context;
} else {
progressBar = args[0];
iterator = args[1];
labelFunc = args[2];
whenDoneFunc = args[3];
context = args[4];
}
if (!context) context = typeof window !== 'undefined' ? window : global;
if (!labelFunc) labelFunc = function(x) { return x; };
// init progressbar
if (!progressBar) {
progressBarAdded = true;
var Global = typeof window !== 'undefined' ? window : global;
var world = Global.lively && lively.morphic && lively.morphic.World.current();
progressBar = world ? world.addProgressBar() : {
setValue: function(val) {},
setLabel: function() {},
remove: function() {}
};
}
progressBar.setValue(0);
// nest functions so that the iterator calls the next after a delay
(array.reduceRight(function(nextFunc, item, idx) {
return function() {
try {
progressBar.setValue(idx / steps);
if (labelFunc) progressBar.setLabel(labelFunc.call(context, item, idx));
iterator.call(context, item, idx);
} catch (e) {
console.error(
'Error in forEachShowingProgress at %s (%s)\n%s\n%s',
idx, item, e, e.stack);
}
nextFunc.delay(0);
};
}, function() {
progressBar.setValue(1);
if (progressBarAdded) (function() { progressBar.remove(); }).delay(0);
if (whenDoneFunc) whenDoneFunc.call(context);
}))();
return array;
}
function swap(array, index1, index2) {
// mutating
// Example:
// var a = [1,2,3,4];
// arr.swap(a, 3, 1);
// a // => [1,4,3,2]
if (index1 < 0) index1 = array.length + index1;
if (index2 < 0) index2 = array.length + index2;
var temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
return array;
}
function rotate(array, times) {
// non-mutating
// Example:
// arr.rotate([1,2,3]) // => [2,3,1]
times = times || 1;
return array.slice(times).concat(array.slice(0,times));
}
// -=-=-=-=-
// grouping
// -=-=-=-=-
function groupBy(array, iterator, context) {
// Applies `iterator` to each element in `array`, and puts the return value
// into a collection (the group) associated to it's stringified representation
// (the "hash").
// See [`Group.prototype`] for available operations on groups.
// Example:
// Example 1: Groups characters by how often they occur in a string:
// var chars = arr.from("Hello World");
// arr.groupBy(arr.uniq(chars), function(c) {
// return arr.count(chars, c); })
// // => {
// // "1": ["H","e"," ","W","r","d"],
// // "2": ["o"],
// // "3": ["l"]
// // }
// // Example 2: Group numbers by a custom qualifier:
// arr.groupBy([3,4,1,7,4,3,8,4], function(n) {
// if (n <= 3) return "small";
// if (n <= 7) return "medium";
// return "large";
// });
// // => {
// // large: [8],
// // medium: [4,7,4,4],
// // small: [3,1,3]
// // }
return Group.fromArray(array, iterator, context);
}
function groupByKey(array, key) {
// var objects = [{x: }]
// arr.groupBy(arr.uniq(chars), function(c) {
// return arr.count(chars, c); })
// // => {
// // "1": ["H","e"," ","W","r","d"],
// // "2": ["o"],
// // "3": ["l"]
// // }
return groupBy(array, ea => ea[key]);
}
function partition(array, iterator, context) {
// Example:
// var array = [1,2,3,4,5,6];
// arr.partition(array, function(ea) { return ea > 3; })
// // => [[1,2,3,4],[5,6]]
iterator = iterator || function(x) { return x; };
var trues = [], falses = [];
array.forEach(function(value, index) {
(iterator.call(context, value, index) ? trues : falses).push(value);
});
return [trues, falses];
}
function batchify(array, constrainedFunc, context) {
// Takes elements and fits them into subarrays (= batches) so that for
// each batch constrainedFunc returns true. Note that contrained func
// should at least produce 1-length batches, otherwise an error is raised
// Example:
// // Assume you have list of things that have different sizes and you want to
// // create sub-arrays of these things, with each sub-array having if possible
// // less than a `batchMaxSize` of combined things in it:
// var sizes = [
// Math.pow(2, 15), // 32KB
// Math.pow(2, 29), // 512MB
// Math.pow(2, 29), // 512MB
// Math.pow(2, 27), // 128MB
// Math.pow(2, 26), // 64MB
// Math.pow(2, 26), // 64MB
// Math.pow(2, 24), // 16MB
// Math.pow(2, 26)] // 64MB
// var batchMaxSize = Math.pow(2, 28)/*256MB*/;
// function batchConstrained(batch) {
// return batch.length == 1 || batch.sum() < batchMaxSize;
// }
// var batches = sizes.batchify(batchConstrained);
// batches.pluck('length') // => [4,1,1,2]
// batches.map(arr.sum).map(num.humanReadableByteSize) // => ["208.03MB","512MB","512MB","128MB"]
return findBatches([], array);
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function extractBatch(batch, sizes) {
// ignore-in-doc
// Array -> Array -> Array[Array,Array]
// case 1: no sizes to distribute, we are done
if (!sizes.length) return [batch, []];
var first = sizes[0], rest = sizes.slice(1);
// if batch is empty we have to take at least one
// if batch and first still fits, add first
var candidate = batch.concat([first]);
if (constrainedFunc.call(context, candidate)) return extractBatch(candidate, rest);
// otherwise leave first out for now
var batchAndSizes = extractBatch(batch, rest);
return [batchAndSizes[0], [first].concat(batchAndSizes[1])];
}
function findBatches(batches, sizes) {
if (!sizes.length) return batches;
var extracted = extractBatch([], sizes);
if (!extracted[0].length)
throw new Error('Batchify constrained does not ensure consumption '
+ 'of at least one item per batch!');
return findBatches(batches.concat([extracted[0]]), extracted[1]);
}
}
function toTuples(array, tupleLength) {
// Creates sub-arrays with length `tupleLength`
// Example:
// arr.toTuples(["H","e","l","l","o"," ","W","o","r","l","d"], 4)
// // => [["H","e","l","l"],["o"," ","W","o"],["r","l","d"]]
tupleLength = tupleLength || 1;
return range(0,Math.ceil(array.length/tupleLength)-1).map(function(n) {
return array.slice(n*tupleLength, n*tupleLength+tupleLength);
}, array);
}
var permutations = (function() {
function computePermutations(restArray, values) {
return !restArray.length ? [values] :
flatmap(restArray, function(ea, i) {
return computePermutations(
restArray.slice(0, i).concat(restArray.slice(i+1)),
values.concat([ea]));
});
}
return function(array) { return computePermutations(array, []); }
})();
function combinationsPick(listOfListsOfValues, pickIndices) {
// Given a "listOfListsOfValues" in the form of an array of arrays and
// `pickIndices` list with the size of the number of arrays which indicates what
// values to pick from each of the arrays, return a list with two values:
// 1. values picked from each of the arrays, 2. the next pickIndices or null if at end
// Example:
// var searchSpace = [["a", "b", "c"], [1,2]];
// arr.combinationsPick(searchSpace, [0,1]);
// // => [["a",2], [1,0]]
// arr.combinationsPick(searchSpace, [1,0]);
// // => [["b",1], [1,1]]
var values = listOfListsOfValues.map(function(subspace, i) {
return subspace[pickIndices[i]]; }),
nextState = pickIndices.slice();
for (var i = listOfListsOfValues.length; i--; i >= 0) {
var subspace = listOfListsOfValues[i], nextIndex = nextState[i] + 1;
if (subspace[nextIndex]) { nextState[i] = nextIndex; break; }
else if (i === 0) { nextState = undefined; break; }
else { nextState[i] = 0; }
}
return [values, nextState];
}
function combinations(listOfListsOfValues) {
// Given a "listOfListsOfValues" in the form of an array of arrays,
// retrieve all the combinations by picking one item from each array.
// This basically creates a search tree, traverses it and gathers all node
// values whenever a leaf node is reached.
// Example:
// lively.lang.arr.combinations([['a', 'b', 'c'], [1, 2]])
// // => [["a", 1], ["a", 2], ["b", 1], ["b", 2], ["c", 1], ["c", 2]]
var size = listOfListsOfValues.reduce(function(prod, space) { return prod * space.length; }, 1),
searchState = listOfListsOfValues.map(function(_) { return 0; }),
results = new Array(size);
for (var i = 0; i < size; i++) {
var result = combinationsPick(listOfListsOfValues, searchState);
results[i] = result[0];
searchState = result[1];
}
return results;
}
function take(arr, n) { return arr.slice(0, n); }
function drop(arr, n) { return arr.slice(n); }
function takeWhile(arr, fun, context) {
var i = 0;;
for (; i < arr.length; i++)
if (!fun.call(context, arr[i], i)) break;
return arr.slice(0, i);
}
function dropWhile(arr, fun, context) {
var i = 0;;
for (; i < arr.length; i++)
if (!fun.call(context, arr[i], i)) break;
return arr.slice(i);
}
// -=-=-=-=-=-
// randomness
// -=-=-=-=-=-
function shuffle(array) {
// Ramdomize the order of elements of array. Does not mutate array.
// Example:
// shuffle([1,2,3,4,5]) // => [3,1,2,5,4]
let unusedIndexes = range(0, array.length-1),
shuffled = Array(array.length);
for (let i = 0; i < array.length; i++) {
var shuffledIndex = unusedIndexes.splice(
Math.round(Math.random() * (unusedIndexes.length-1)), 1);
shuffled[shuffledIndex] = array[i];
}
return shuffled;
}
// -=-=-=-=-=-=-=-
// Number related
// -=-=-=-=-=-=-=-
function max(array, iterator, context) {
// Example:
// var array = [{x:3,y:2}, {x:5,y:1}, {x:1,y:5}];
// arr.max(array, function(ea) { return ea.x; }) // => {x: 5, y: 1}
iterator = iterator || function(x) { return x; };
var result;
array.reduce(function(max, ea, i) {
var val = iterator.call(context, ea, i);
if (typeof val !== "number" || val <= max) return max;
result = ea; return val;
}, -Infinity);
return result;
}
function min(array, iterator, context) {
// Similar to `arr.max`.
iterator = iterator || (x => x);
return max(array, (ea, i) => -iterator.call(context, ea, i));
}
function sum(array) {
// show-in-doc
var sum = 0;
for (var i = 0; i < array.length; i++)
sum += array[i];
return sum;
}
function count(array, item) {
return array.reduce(function(count, ea) {
return ea === item ? count + 1 : count; }, 0);
}
function size(array) { return array.length; }
function histogram(data, binSpec) {
// ignore-in-doc
// Without a `binSpec` argument partition the data
// var numbers = arr.genN(10, num.random);
// var numbers = arr.withN(10, "a");
// => [65,73,34,94,92,31,27,55,95,48]
// => [[65,73],[34,94],[92,31],[27,55],[95,48]]
// => [[82,50,16],[25,43,77],[40,64,31],[51,39,13],[17,34,87],[51,33,30]]
if (typeof binSpec === 'undefined' || typeof binSpec === 'number') {
var binNumber = binSpec || (function sturge() {
return Math.ceil(Math.log(data.length) / Math.log(2) + 1);
})(data);
var binSize = Math.ceil(Math.round(data.length / binNumber));
return range(0, binNumber-1).map(function(i) {
return data.slice(i*binSize, (i+1)*binSize);
});
} else if (binSpec instanceof Array) {
// ignore-in-doc
// bins specifies n threshold values that will create n-1 bins.
// Each data value d is placed inside a bin i if:
// threshold[i] >= d && threshold[i+1] < d
var thresholds = binSpec;
return data.reduce(function(bins, d) {
if (d < thresholds[1]) { bins[0].push(d); return bins; }
for (var i = 1; i < thresholds.length; i++) {
if (d >= thresholds[i] && (!thresholds[i+1] || d <= thresholds[i+1])) {
bins[i].push(d); return bins;
}
}
throw new Error(`Histogram creation: Cannot group data ${d} into thresholds ${thresholds}`);
}, range(1,thresholds.length).map(function() { return []; }))
}
}
// -=-=-=-=-
// Copying
// -=-=-=-=-
function clone(array) {
// shallow copy
return [].concat(array);
}
// -=-=-=-=-=-
// conversion
// -=-=-=-=-=-
function toArray(array) { return from(array); }
// -=-=-=-=-=-
// DEPRECATED
// -=-=-=-=-=-
function each(arr, iterator, context) {
return arr.forEach(iterator, context);
}
function all(arr, iterator, context) {
return arr.every(iterator, context);
}
function any(arr, iterator, context) {
return arr.some(iterator, context);
}
function collect(arr, iterator, context) {
return arr.map(iterator, context);
}
function findAll(arr, iterator, context) {
return arr.filter(iterator, context);
}
function inject(array, memo, iterator, context) {
if (context) iterator = iterator.bind(context);
return array.reduce(iterator, memo);
}
// asynch methods
function mapAsyncSeries(array, iterator, callback) {
// Apply `iterator` over `array`. Unlike `mapAsync` the invocation of
// the iterator happens step by step in the order of the items of the array
// and not concurrently.
// ignore-in-doc
// Could simply be:
// return exports.arr.mapAsync(array, {parallel: 1}, iterator, callback);
// but the version below is 2x faster
var result = [], callbackTriggered = false;
return array.reduceRight(function(nextFunc, ea, idx) {
if (callbackTriggered) return;
return function(err, eaResult) {
if (err) return maybeDone(err);
if (idx > 0) result.push(eaResult);
try {
iterator(ea, idx, once(nextFunc));
} catch (e) { maybeDone(e); }
}
}, function(err, eaResult) {
result.push(eaResult);
maybeDone(err, true);
})();
function maybeDone(err, finalCall) {
if (callbackTriggered || (!err && !finalCall)) return;
callbackTriggered = true;
try { callback(err, result); } catch (e) {
console.error("Error in mapAsyncSeries - callback invocation error:\n" + (e.stack || e));
}
}
}
function mapAsync(array, options, iterator, callback) {
// Apply `iterator` over `array`. In each iterator gets a callback as third
// argument that should be called when the iteration is done. After all
// iterators have called their callbacks, the main `callback` function is
// invoked with the result array.
// Example:
// lively.lang.arr.mapAsync([1,2,3,4],
// function(n, i, next) { setTimeout(function() { next(null, n + i); }, 20); },
// function(err, result) { /* result => [1,3,5,7] */ });
if (typeof options === "function") {
callback = iterator;
iterator = options;
options = null;
}
options = options || {};
if (!array.length) return callback && callback(null, []);
if (!options.parallel) options.parallel = Infinity;
var results = [], completed = [],
callbackTriggered = false,
lastIteratorIndex = 0,
nActive = 0;
var iterators = array.map(function(item, i) {
return function() {
nActive++;
try {
iterator(item, i, once(function(err, result) {
results[i] = err || result;
maybeDone(i, err);
}));
} catch (e) { maybeDone(i, e); }
}
});
return activate();
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function activate() {
while (nActive < options.parallel && lastIteratorIndex < array.length)
iterators[lastIteratorIndex++]();
}
function maybeDone(idx, err) {
if (completed.indexOf(idx) > -1) return;
completed.push(idx);
nActive--;
if (callbackTriggered) return;
if (!err && completed.length < array.length) { activate(); return; }
callbackTriggered = true;
try { callback && callback(err, results); } catch (e) {
console.error("Error in mapAsync - main callback invocation error:\n" + (e.stack || e));
}
}
}
// poly-filling...
if (!features.from) Array.from = from;
if (!features.filter) Array.prototype.filter = function(it, ctx) { return filter(this, it, ctx); };
if (!features.find) Array.prototype.find = function(it, ctx) { return detect(this, it, ctx); };
if (!features.findIndex) Array.prototype.findIndex = function(it, ctx) { return findIndex(this, it, ctx); };
if (!features.includes) Array.prototype.includes = function(x) { return includes(this, x); };
export {
range,
from,
withN,
genN,
filter,
detect,
findIndex,
findAndGet,
filterByKey,
grep,
mask,
reject,
rejectByKey,
without,
withoutAll,
uniq,
uniqBy,
uniqByKey,
compact,
mutableCompact,
forEach,
zip,
flatten,
flatmap,
interpose,
delimWith,
map,
invoke,
pluck,
reduce,
reduceRight,
isArray,
includes,
include,
some,
every,
equals,
deepEquals,
isSorted,
sort,
sortBy,
sortByKey,
reverse,
reversed,
reMatches,
first,
last,
intersect,
union,
pushAt,
removeAt,
remove,
pushAll,
pushAllAt,
pushIfNotIncluded,
replaceAt,
clear,
isSubset,
doAndContinue,
nestedDelay,
forEachShowingProgress,
swap,
rotate,
groupBy,
groupByKey,
partition,
batchify,
toTuples,
permutations,
combinationsPick,
combinations,
take,
drop,
takeWhile,
dropWhile,
shuffle,
max,
min,
sum,
count,
size,
histogram,
clone,
toArray,
each,
all,
any,
collect,
findAll,
inject,
mapAsyncSeries,
mapAsync,
}