/**
FUNCTIONAL
by Caio Chassot (http://v2studio.com/k/code/)
*/
/**
* this is used internally by each, map, combine, filter and reduce to accept
* strings as functions.
*
* if fn does not contain a return statement, a return keyword will be added
* before the last statement. the last statement is determined by removing the
* trailing semicolon (';') (if it exists) and then searching for the last
* semicolon, hence, caveats may apply (i.e. if the last statement has a
* string or regex containing the ';' character things will go wrong)
* @param args a string of comma separated names of the function arguments
* @param fn the function body
*/
function __strfn(args, fn) {
/**
* Internal function. Do not call it directly.
*/
function quote(s) { return '"' + s.replace(/"/g,'\\"') + '"' }
if (!/\breturn\b/.test(fn)) {
fn = fn.replace(/;\s*$/, '');
fn = fn.insert(fn.lastIndexOf(';')+1, ' return ');
}
return eval('new Function('
+ map(args.split(/\s*,\s*/), quote).join()
+ ','
+ quote(fn)
+ ')'
);
}
/**
* traverses list, applying fn to each item of list.
* see doc for __strfn for peculiarities about passing strings for fn
*
* each provides a safe way for traversing only an array's indexed items,
* ignoring its other properties. (as opposed to how for-in works)
* @param list anything that can be indexed and has a length property. usually an array.
* @param fn either a function, or a string containing a function body,
* in which case the name of the paremeters passed to it will be
* 'item', 'idx' and 'list'.
* @see #__strfn
*/
function each(list, fn) {
if (typeof(fn)=='string') return each(list, __strfn('item,idx,list', fn));
for (var i=0; i < list.length; i++) fn(list[i], i, list);
}
/**
* traverses list, applying fn to each item of list, returning an array
of values returned by fn
parameters work the same as for each, same __strfn caveats apply
if fn is not provided, the list item is returned itself. this is an easy
way to transform fake arrays (e.g. the arguments object of a function or
nodeList objects) into real javascript arrays.
e.g.: args = map(arguments)
If you don't care about map's return value, you should use each
this is a simplified version of python's map. parameter order is different,
only a single list (array) is accepted, and the parameters passed to [fn]
are different:
[fn] takes the current item, then, optionally, the current index and a
reference to the list (so that [fn] can modify list)
see combine if you want to pass multiple lists
*/
function map(list, fn) {
if (typeof(fn)=='string') return map(list, __strfn('item,idx,list', fn));
var result = [];
fn = fn || function(v) {return v};
for (var i=0; i < list.length; i++) result.push(fn(list[i], i, list));
return result;
}
/**
* combine will traverse all lists concurrently, passing each row if items as
parameters to fn
if fn is not provided, a function that returns a list containing each
item in the row is used.
if a list is smaller than the other, null is used in place of its missing
items
* @param list one or more lists (see each for definition of a list)
* @param fn Similar s each or map, a function or a string containing
a function body.
if a string is used, the name of parameters passed to the
created function will be the lowercase alphabet letters, in
order: a,b,c...
same __strfn caveats apply
* @see #each
* @see #__strfn
* @return an array of the values returned by calling fn for each row of items
*//*
function combine() {
var args = map(arguments);
var lists = map(args.slice(0,-1),'map(item)');
var fn = args.last();
var toplen = map(lists, "item.length").max();
var vals = [];
if (!fn) fn = function(){return map(arguments)};
if (typeof fn == 'string') {
if (lists.length > 26) throw 'string functions can take at most 26 lists';
var a = 'a'.charCodeAt(0);
fn = __strfn(map(range(a, a+lists.length),'String.fromCharCode(item)').join(','), fn);
}
map(lists, function(li) {
while (li.length < toplen) li.push(null);
map(li, function(item,ix){
if (ix < vals.length) vals[ix].push(item);
else vals.push([item]);
});
});
return map(vals, function(val) { return fn.apply(fn, val) });
}
/**
* returns an array of items in list for which fn(item) is true
parameters work the same as for each, same __strfn caveats apply
if fn is not specified the items are evaluated themselves, that is,
filter will return an array of the items in list which evaluate to true
this is a similar to python's filter, but parameter order is inverted
*//*
function filter(list, fn) {
if (typeof(fn)=='string') return filter(list, __strfn('item,idx,list', fn));
var result = [];
fn = fn || function(v) {return v};
map(list, function(item,idx,list) { if (fn(item,idx,list)) result.push(item) } );
return result;
}
/**
* similar to python's reduce. paremeter order inverted...
* @param list see doc for each to learn more about it
* @param initial TODO
* @param fn similar to each too, but in the case where it's a string,
the name of the paremeters passed to it will be 'a' and 'b'
same __strfn caveats apply
*//*
function reduce(list, initial, fn) {
if (undef(fn)) {
fn = initial;
// explicit window object so browsers that do not have an undefined
//keyword will evaluate to the (hopefully) undefined parameter
//undefined of window
initial = window.undefined;
}
if (typeof(fn)=='string') return reduce(list, initial, __strfn('a,b', fn));
if (isdef(initial)) list.splice(0,0,initial);
if (list.length===0) return false;
if (list.length===1) return list[0];
var result = list[0];
var i = 1;
while(i