diff --git a/asx/src/asx/array/inGroupsOf.as b/asx/src/asx/array/inGroupsOf.as index 2ff4bbd..d64ba60 100644 --- a/asx/src/asx/array/inGroupsOf.as +++ b/asx/src/asx/array/inGroupsOf.as @@ -1,27 +1,30 @@ package asx.array { + import asx.fn.aritize; + import asx.fn.partial; + import asx.object.newInstance; + /** - * + * */ public function inGroupsOf(iterable:Object, size:int):Array { - var i:int = 0; - var group:Array = []; - var groups:Array = [group]; - - for each (var item:Object in iterable) - { - group[group.length] = item; - i++; - - if (i == size) - { - i = 0; - group = []; - groups[groups.length] = group; + if(size < 1) size = 1; + + const l:int = len(iterable); + const groups:Array = map(range(Math.ceil(l / size)), aritize(partial(newInstance, Array), 0)); + + var group:Array; + var i:int = -1; + + for each(var item:Object in iterable) { + if(++i % size == 0) { + group = groups[i / size]; } - } + group[group.length] = item; + } + return groups; } } \ No newline at end of file diff --git a/asx/src/asx/array/inject.as b/asx/src/asx/array/inject.as index 905fb3d..18dbd03 100644 --- a/asx/src/asx/array/inject.as +++ b/asx/src/asx/array/inject.as @@ -1,4 +1,5 @@ package asx.array { + import asx.fn._; /** * Injects the memo value into an iterator function that is applied to every @@ -21,11 +22,14 @@ package asx.array { * }); * */ - public function inject(memo:Object, iterable:Object, iterator:Function):Object { + public function inject(seed:Object, iterable:Object, iterator:Function):Object { var i:int = 0; var n:int = iterator.length; + var memo:Object = seed; for each (var value:Object in iterable) { - memo = iterator.apply(null, [ memo, value, i ].slice(0, Math.max(2, n))); + memo = seed == _ && i == 0 ? + value : + iterator.apply(null, [ memo, value, i ].slice(0, Math.max(2, n))); i++; } return memo; diff --git a/asx/src/asx/array/max.as b/asx/src/asx/array/max.as index e34e236..b214a24 100644 --- a/asx/src/asx/array/max.as +++ b/asx/src/asx/array/max.as @@ -1,24 +1,26 @@ package asx.array { + import asx.fn._; + /** * Find the max item, or item with the max field value. */ public function max(iterable:Object, field:String=null):Object { - var result:Object; + var result:Object = _; var item:Object; if (field) { for each (item in iterable) { - if (!result) + if (result === _) { - result = item; + result = item[field]; } - else if (item && item[field] > result[field]) + else if (item && item[field] > result) { - result = item; + result = item[field]; } } } @@ -26,7 +28,7 @@ package asx.array { for each (item in iterable) { - if (!result) + if (result === _) { result = item; } diff --git a/asx/src/asx/array/min.as b/asx/src/asx/array/min.as index e382c33..2b559fe 100644 --- a/asx/src/asx/array/min.as +++ b/asx/src/asx/array/min.as @@ -1,24 +1,26 @@ package asx.array { + import asx.fn._; + /** * Find the min item, or item with the min field value. */ public function min(iterable:Object, field:String=null):Object { - var result:Object; + var result:Object = _; var item:Object; if (field) { for each (item in iterable) { - if (!result) + if (result === _) { - result = item; + result = item[field]; } - else if (item && item[field] < result[field]) + else if (item && item[field] < result) { - result = item; + result = item[field]; } } } @@ -26,7 +28,7 @@ package asx.array { for each (item in iterable) { - if (!result) + if (result === _) { result = item; } diff --git a/asx/src/asx/array/permutate.as b/asx/src/asx/array/permutate.as new file mode 100644 index 0000000..cbb4b2c --- /dev/null +++ b/asx/src/asx/array/permutate.as @@ -0,0 +1,65 @@ +package asx.array +{ + import flash.utils.Dictionary; + + import asx.fn._; + import asx.fn.partial; + + /** + * Returns a multi-dimensional Array of all combinations of values in the Array, + * + *
Example:
+ *+ * trace(permutate([1, 2, 3], 2)); //[ [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3] ] + * trace(permutate([1, 2, 3], 3)); + * // yields: + * //[ + * // [1, 1, 1], [1, 1, 2], [1, 1, 3], + * // [1, 2, 1], [1, 2, 2], [1, 2, 3], + * // [1, 3, 1], [1, 3, 2], [1, 3, 3], + * // [2, 1, 1], [2, 1, 2], [2, 1, 3], + * // [2, 2, 1], [2, 2, 2], [2, 2, 3], + * // [2, 3, 1], [2, 3, 2], [2, 3, 3], + * // [3, 1, 1], [3, 1, 2], [3, 1, 3], + * // [3, 2, 1], [3, 2, 2], [3, 2, 3], + * // [3, 3, 1], [3, 3, 2], [3, 3, 3] + * //] + *+ */ + public function permutate(array:Array, combinations:int = 2):Array { + + const size:int = length(array); + + combinations = (combinations < 2) ? 2 : combinations; + + if(size < 2) + return array; + + if(size < combinations) + return [array]; + + // Initialize a cache to store the permutations for each unique value. + // If we encounter that value again, we won't have to iterate, reducing + // the runtime from O(n^combinations) to O((n - duplicates)^combinations) + const cache:Dictionary = new Dictionary(); + + const permutations:Array = []; + const merge:Function = [].concat; + + const recurse:Function = function(array:Array, togo:int, values:Array):Array { + return togo < 1 ? + [values] : + merge.apply(null, map(array, function(x:Object):Array { + return recurse(array, togo - 1, values.concat(x)); + })); + }; + + // Do a merge/map instead of calling recurse so that we can cache + // the results of unique value permutations. + return merge.apply(null, map(array, function(e:*):Array { + return e in cache ? + cache[e] : + cache[e] = recurse(array, combinations - 1, [e]); + })); + } +} diff --git a/asx/src/asx/array/pluck.as b/asx/src/asx/array/pluck.as index 0314484..8761968 100644 --- a/asx/src/asx/array/pluck.as +++ b/asx/src/asx/array/pluck.as @@ -1,5 +1,7 @@ package asx.array { + import asx.fn._; + import asx.fn.partial; /** * Extracts the value of a field from every item in an Array. @@ -14,21 +16,26 @@ package asx.array * assertThat(plucked, equalTo(['waffles', 'crumpets', 'toast'])); * */ - public function pluck(iterable:Object, field:Object):Array + public function pluck(iterable:Object, field:Object, ...args):Array { var chain:Array = field is Array ? field as Array : String(field).split('.'); - return inject(iterable, chain, $pluckIt) as Array; + return inject(iterable, chain, partial($pluckIt, _, _, args)) as Array; } } import asx.array.map; +import asx.fn.callFunction; import asx.fn.callProperty; import asx.fn.getProperty; -internal function $pluckIt(iterable:Object, field:String):Array +internal function $pluckIt(iterable:Object, field:String, args:Array = null):Array { var isMethod:Boolean = !!field.match(/^.+\(\)$/); field = isMethod ? field.slice(0, -2) : field; - return map(iterable, isMethod ? callProperty(field) : getProperty(field)); + return map(iterable, + isMethod ? + callProperty.apply(null, [field].concat(args)) : + getProperty(field) + ); } diff --git a/asx/src/asx/array/toDictionary.as b/asx/src/asx/array/toDictionary.as index 76434cc..af94f25 100644 --- a/asx/src/asx/array/toDictionary.as +++ b/asx/src/asx/array/toDictionary.as @@ -1,5 +1,6 @@ package asx.array { + import asx.fn.I; import asx.fn.getProperty; import flash.utils.Dictionary; @@ -20,7 +21,7 @@ package asx.array * trace(dict[3].value); // 5 * */ - public function toDictionary(iterable:Object, key:Object):Dictionary + public function toDictionary(iterable:Object, key:Object, value:Object = null):Dictionary { var dict:Dictionary = new Dictionary(); @@ -29,8 +30,10 @@ package asx.array ? key as Function : getProperty(String(key)); + var valFn:Function = value is Function ? value as Function : I; + for each (var item:Object in iterable) - dict[ keyFn(item) ] = item; + dict[ keyFn(item) ] = valFn(item); return dict; } diff --git a/asx/src/asx/fn/_.as b/asx/src/asx/fn/_.as index ebf25eb..37a9ea0 100644 --- a/asx/src/asx/fn/_.as +++ b/asx/src/asx/fn/_.as @@ -5,5 +5,7 @@ package asx.fn { * * @see asx.fn.partial */ - public var _:* = undefined; + // don't use undefined because the AVM automatically casts undefined to + // false-y values and we lose the uniqueness of the constant; + public var _:Object = {}; } diff --git a/asx/src/asx/fn/apply.as b/asx/src/asx/fn/apply.as new file mode 100644 index 0000000..2fb6288 --- /dev/null +++ b/asx/src/asx/fn/apply.as @@ -0,0 +1,11 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function apply(fn:Function):Function { + return function(array:Array):* { + return fn.apply(null, array || []); + } + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/areEqual.as b/asx/src/asx/fn/areEqual.as new file mode 100644 index 0000000..d418f84 --- /dev/null +++ b/asx/src/asx/fn/areEqual.as @@ -0,0 +1,20 @@ +package asx.fn +{ + import asx.array.allOf; + import asx.array.head; + import asx.array.last; + import asx.array.permutate; + + /** + * @author ptaylor + */ + public function areEqual(...items):Boolean { + + if(items.length < 2) return head(items); + if(items.length == 2) return head(items) === last(items); + + const permutations:Array = permutate(items, 2); + + return allOf(permutations, apply(areEqual)); + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/args.as b/asx/src/asx/fn/args.as new file mode 100644 index 0000000..38b39d6 --- /dev/null +++ b/asx/src/asx/fn/args.as @@ -0,0 +1,11 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function args(...args):Array { + return merge.apply(null, args); + } +} + +internal const merge:Function = [].concat; \ No newline at end of file diff --git a/asx/src/asx/fn/bindSetProp.as b/asx/src/asx/fn/bindSetProp.as new file mode 100644 index 0000000..a84e8a0 --- /dev/null +++ b/asx/src/asx/fn/bindSetProp.as @@ -0,0 +1,11 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function bindSetProp(instance:Object, field:String):Function { + return function(val:*):void { + instance[field] = val; + }; + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/callXMLProperty.as b/asx/src/asx/fn/callXMLProperty.as new file mode 100644 index 0000000..5c84945 --- /dev/null +++ b/asx/src/asx/fn/callXMLProperty.as @@ -0,0 +1,22 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function callXMLProperty(method:String, ...args):* { + return function(node:XML):* { + switch(method) { + case 'children': + return node.children(); + case 'childIndex': + return node.childIndex(); + case 'elements': + return node.elements(); + case 'localName': + return node.localName(); + case 'toString': + return node.toString(); + } + }; + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/distribute.as b/asx/src/asx/fn/distribute.as new file mode 100644 index 0000000..ec422b6 --- /dev/null +++ b/asx/src/asx/fn/distribute.as @@ -0,0 +1,13 @@ +package asx.fn +{ + import asx.array.map; + + /** + * @author ptaylor + */ + public function distribute(...fns):Function { + return function(...args):Array { + return map(fns, callFunction.apply(null, args)); + } + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/getProperty.as b/asx/src/asx/fn/getProperty.as index 32d4263..c8eb107 100644 --- a/asx/src/asx/fn/getProperty.as +++ b/asx/src/asx/fn/getProperty.as @@ -1,4 +1,5 @@ package asx.fn { + import asx.array.inject; /** * Returns a function that when called will get the property on the item. @@ -10,9 +11,15 @@ package asx.fn { * assertThat(results, equalTo([3, 2, 1])); * */ - public function getProperty(property:String):Function { - return function(item:Object, ...rest):Object { - return item[property]; - }; + public function getProperty(property:Object):Function { + var chain:Array = property is Array ? property as Array : String(property).split('.'); + + const getProp:Function = function(item:Object, field:*):Object { + return item[field]; + }; + + return function(item:Object, ...rest):Object { + return inject(item, chain, getProp); + }; } } diff --git a/asx/src/asx/fn/guard.as b/asx/src/asx/fn/guard.as new file mode 100644 index 0000000..e0f33aa --- /dev/null +++ b/asx/src/asx/fn/guard.as @@ -0,0 +1,11 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function guard(fn:Function, ...args):Function { + return function(...a):* { + return fn.apply(null, a.slice(0, fn.length || a.length)); + } + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/ifElse.as b/asx/src/asx/fn/ifElse.as new file mode 100644 index 0000000..8ca5ccf --- /dev/null +++ b/asx/src/asx/fn/ifElse.as @@ -0,0 +1,13 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function ifElse(condition:Function, then:Function, otherwise:Function):Function { + return function(...args):* { + return condition.apply(null, args) ? + then.apply(null, args) : + otherwise.apply(null, args); + } + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/memoize.as b/asx/src/asx/fn/memoize.as new file mode 100644 index 0000000..c968320 --- /dev/null +++ b/asx/src/asx/fn/memoize.as @@ -0,0 +1,18 @@ +package asx.fn +{ + import flash.utils.Dictionary; + + /** + * @author ptaylor + */ + public function memoize(func:Function, hasher:Function):Function { + const memo:Dictionary = new Dictionary(true); + + return function(...args):* { + const key:Object = hasher.apply(null, args); + return memo[key] == null ? + memo[key] = func.apply(null, args) : + memo[key]; + }; + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/partial.as b/asx/src/asx/fn/partial.as index e3b88ce..93141b4 100644 --- a/asx/src/asx/fn/partial.as +++ b/asx/src/asx/fn/partial.as @@ -12,7 +12,8 @@ package asx.fn { var subpos:Array = []; var value:*; for (var i:int = 0; i < args.length; i++) { - if (args[i] === undefined) { + if (args[i] === undefined || args[i] === _) { + args[i] = _; subpos.push(i); } } @@ -22,11 +23,12 @@ package asx.fn { specialized[subpos[i]] = more[i]; } for (var j:int = 0; j < specialized.length; j++) { - if (specialized[j] === undefined) { + if (specialized[j] === undefined || specialized[j] === _) { + specialized[j] = _; return partial.apply(null, [fn].concat(specialized)); } } - return fn.apply(null, specialized.slice(0, args.length)); + return fn.apply(null, specialized); } }; } diff --git a/asx/src/asx/fn/pull.as b/asx/src/asx/fn/pull.as new file mode 100644 index 0000000..0a790fe --- /dev/null +++ b/asx/src/asx/fn/pull.as @@ -0,0 +1,12 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function pull(instance:Object, field:String, ...args):Function { + return function(...rest):* { + const val:* = instance[field]; + return val is Function ? val.apply(instance, args) : val; + } + } +} \ No newline at end of file diff --git a/asx/src/asx/fn/sequence.as b/asx/src/asx/fn/sequence.as index fc686da..a497a7d 100644 --- a/asx/src/asx/fn/sequence.as +++ b/asx/src/asx/fn/sequence.as @@ -7,10 +7,11 @@ package asx.fn { * sequence(f1, f2, f3, ...fn)(...args) == fn(...(f3(f2(f1(...args))))) */ public function sequence(...fns):Function { - + var val:*; return function(...args):Object { return inject(args, fns, function(memo:Array, fn:Function):Array { - return [fn.apply(null, memo)]; + val = fn.apply(null, memo); + return val === _ ? memo : [val]; })[0]; }; } diff --git a/asx/src/asx/fn/tap.as b/asx/src/asx/fn/tap.as new file mode 100644 index 0000000..b41e010 --- /dev/null +++ b/asx/src/asx/fn/tap.as @@ -0,0 +1,12 @@ +package asx.fn +{ + /** + * @author ptaylor + */ + public function tap(fn:Function, returnValue:*):Function { + return function(...args):* { + fn.apply(null, args); + return returnValue; + } + } +} \ No newline at end of file diff --git a/asx/src/asx/object/isAn.as b/asx/src/asx/object/isAn.as new file mode 100644 index 0000000..ed8805e --- /dev/null +++ b/asx/src/asx/object/isAn.as @@ -0,0 +1,18 @@ +package asx.object { + + /** + * Returns a function that returns true if the item is the given type. + * + * @example + *