diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..3163c22 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,14 @@ + +# Directories # +############### +build/ +reports/ +dist/ + +# Node.js # +########### +/node_modules/ + +# Git # +####### +.git* diff --git a/.npmignore b/.npmignore index 42781b3..9db298d 100644 --- a/.npmignore +++ b/.npmignore @@ -13,6 +13,7 @@ examples/ reports/ support/ test/ +benchmark/ # Node.js # ########### @@ -46,4 +47,6 @@ Desktop.ini # Utilities # ############# .jshintrc -.travis.yml \ No newline at end of file +.jshintignore +.travis.yml +.editorconfig diff --git a/.travis.yml b/.travis.yml index 7c16620..29cff27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: node_js node_js: - - "0.10" + - '0.12' + - '0.11' + - '0.10' + - '0.8' + - 'iojs' +before_install: + - npm update -g npm after_script: - - npm run coveralls \ No newline at end of file + - npm run coveralls + diff --git a/LICENSE b/LICENSE index 2fe3939..93cf2ea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Athan Reines. +Copyright (c) 2014-2015 The Compute.io Authors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/Makefile b/Makefile index f5648d1..c6f009c 100644 --- a/Makefile +++ b/Makefile @@ -5,25 +5,29 @@ # Set the node.js environment to test: NODE_ENV ?= test +# Kernel name: +KERNEL ?= $(shell uname -s) + +ifeq ($(KERNEL), Darwin) + OPEN ?= open +else + OPEN ?= xdg-open +endif # NOTES # -NOTES ?= 'TODO|FIXME' +NOTES ?= 'TODO|FIXME|WARNING|HACK|NOTE' # MOCHA # -# Specify the test framework bin locations: MOCHA ?= ./node_modules/.bin/mocha _MOCHA ?= ./node_modules/.bin/_mocha - -# Specify the mocha reporter: MOCHA_REPORTER ?= spec # ISTANBUL # -# Istanbul configuration: ISTANBUL ?= ./node_modules/.bin/istanbul ISTANBUL_OUT ?= ./reports/coverage ISTANBUL_REPORT ?= lcov @@ -31,6 +35,12 @@ ISTANBUL_LCOV_INFO_PATH ?= $(ISTANBUL_OUT)/lcov.info ISTANBUL_HTML_REPORT_PATH ?= $(ISTANBUL_OUT)/lcov-report/index.html +# JSHINT # + +JSHINT ?= ./node_modules/.bin/jshint +JSHINT_REPORTER ?= ./node_modules/jshint-stylish + + # FILES # @@ -96,9 +106,20 @@ test-istanbul-mocha: node_modules view-cov: view-istanbul-report view-istanbul-report: - open $(ISTANBUL_HTML_REPORT_PATH) + $(OPEN) $(ISTANBUL_HTML_REPORT_PATH) +# LINT # + +.PHONY: lint lint-jshint + +lint: lint-jshint + +lint-jshint: node_modules + $(JSHINT) \ + --reporter $(JSHINT_REPORTER) \ + ./ + # NODE # @@ -117,7 +138,6 @@ clean-node: # CLEAN # - .PHONY: clean clean: diff --git a/README.md b/README.md index a22ceeb..1391390 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ csum === [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Dependencies][dependencies-image]][dependencies-url] -> Computes the cumulative sum of a numeric array. +> Computes the cumulative sum. ## Installation @@ -16,31 +16,173 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse ## Usage -To use the module, ``` javascript var csum = require( 'compute-csum' ); ``` -#### csum( arr ) +#### csum( x[, options] ) -Computes the cumulative sum of a numeric `array`. +Computes the cumulative sum. `x` may be either an [`array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays), or [`matrix`](https://github.com/dstructs/matrix). ``` javascript -var data = [ 1, 2, 3, 4 ]; +var data, arr; -csum( data ); +data = [ 1, 2, 3, 4 ]; +arr = csum( data ); // returns [ 1, 3, 6, 10 ] ``` +The function accepts the following `options`: + +* __copy__: `boolean` indicating whether to return a new data structure containing the cumulative sums. Default: `true`. +* __accessor__: accessor `function` for accessing numerical values in object `arrays`. +* __dim__: dimension along which to compute the cumulative sum when provided a matrix. Default: `2` (along the columns). +* __dtype__: output data type. Default: `float64`. + +For non-numeric `arrays`, provide an accessor `function` for accessing `numeric` values. + +``` javascript +var arr = [ + {'x':1}, + {'x':2}, + {'x':3}, + {'x':4}, +]; + +function getValue( d ) { + return d.x; +} + +var sum = csum( arr, { + 'accessor': getValue +}); +// returns [ 1, 3, 6, 10 ] +``` + +__Note__: the function returns an `array` with a length equal to the original input `array`. + +By default, the function computes the cumulative sum for a [`matrix`](https://github.com/dstructs/matrix) along the columns (`dim=2`). + +``` javascript +var matrix = require( 'dstructs-matrix' ), + data, + mat, + out, + i; + +data = new Int8Array( 9 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = i; +} +mat = matrix( data, [3,3], 'int8' ); +/* + [ 0 1 2 + 3 4 5 + 6 7 8 ] +*/ + +out = csum( mat ); +/* + [ 0 1 3 + 3 7 12 + 6 13 21 ] +*/ +``` + +To compute the cumulative sum along the rows, set the `dim` option to `1`. + +``` javascript +out = csum( mat, { + 'dim': 1 +}); +/* + [ 0 1 2 + 3 5 7 + 9 12 15 ] +*/ +``` + +By default, the output data type is `float64`. To specify a different data type, set the `dtype` option (see [`matrix`](https://github.com/dstructs/matrix) for a list of acceptable data types). + +``` javascript +out = csum( mat, { + 'dim': 1, + 'dtype': 'uint8' +}); +/* + [ 0 1 2 + 3 5 7 + 9 12 15 ] +*/ + +var dtype = mu.dtype; +// returns 'uint8' + +out = csum( [ 1, 2, 3, 4 ], { + 'dtype': 'uint8' +}) +// returns Uint8Array( [ 1,3,6,10] ) +``` + +By default, the function returns a new data structure. To mutate the input data structure (e.g., when input values can be discarded or when optimizing memory usage), set the `copy` option to `false`. + +``` javascript +var data, + bool, + mat, + out, + i; + +data = [ 1, 2, 3, 4 ]; + +out = csum( data, { + 'copy': false +}); +// returns [ 1, 3, 6, 10 ] + +bool = ( data === out ); +// returns true + +data = new Int16Array( 9 ); +for ( i = 0; i < 9; i++ ) { + data[ i ] = i; +} +mat = matrix( data, [3,3], 'int16' ); +/* + [ 0 1 2 + 3 4 5 + 6 7 8 ] +*/ + +out = csum( mat, { + 'copy': false +}); +/* + [ 0 1 3 + 3 7 12 + 6 13 21 ] +*/ + +bool = ( mat === out ); +// returns true +``` ## Examples ``` javascript -var csum = require( 'compute-csum' ); +var matrix = require( 'dstructs-matrix' ), + csum = require( 'compute-csum' ), + cast = require( 'compute-cast-arrays' ); -var data = new Array( 1000 ); +var data, + mat, + out, + tmp, + i; +// Plain arrays... +var data = new Array( 1000 ); for ( var i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random()*100 ); } @@ -49,8 +191,52 @@ data.sort( function sort( a, b ) { return a - b; }); -console.log( csum( data ) ); -// returns [...] +out = csum( data ); + +// Object arrays (accessors)... +function getValue( d ) { + return d.x; +} +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; +} +out = csum( data, { + 'accessor': getValue +}); + +// Typed arrays... +var data = new Array( 1000 ); +for ( var i = 0; i < data.length; i++ ) { + data[ i ] = Math.round( Math.random()*100 ); +} +data.sort( function sort( a, b ) { + return a - b; +}); +var data = cast( data, 'int32' ); +tmp = csum( data ); +out = ''; +for ( i = 0; i < data.length; i++ ) { + out += tmp[ i ]; + if ( i < data.length-1 ) { + out += ','; + } +} + +// Matrices... +mat = matrix( data, [100, 10], 'int32' ); +out = csum( mat ); +console.log( 'Matrix (along columns): %s\n', out.toString() ); + +out = csum( mat, { + 'dim': 1 +}); + +// Matrices (custom output data type)... +out = csum( mat, { + 'dtype': 'uint8' +}); ``` To run the example code from the top-level application directory, @@ -69,7 +255,7 @@ The function returns an `array` with a length equal to the original input `array ### Unit -Unit tests use the [Mocha](http://visionmedia.github.io/mocha) test framework with [Chai](http://chaijs.com) assertions. To run the tests, execute the following command in the top-level application directory: +Unit tests use the [Mocha](http://mochajs.org) test framework with [Chai](http://chaijs.com) assertions. To run the tests, execute the following command in the top-level application directory: ``` bash $ make test @@ -93,16 +279,15 @@ $ make view-cov ``` +--- ## License -[MIT license](http://opensource.org/licenses/MIT). +[MIT license](http://opensource.org/licenses/MIT). ---- ## Copyright -Copyright © 2014. Athan Reines. - +Copyright © 2014-2015. The [Compute.io](https://github.com/compute-io) Authors. [npm-image]: http://img.shields.io/npm/v/compute-csum.svg [npm-url]: https://npmjs.org/package/compute-csum diff --git a/examples/index.js b/examples/index.js index 2d8ba90..939ac5d 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,9 +1,18 @@ 'use strict'; -var csum = require( './../lib' ); +var matrix = require( 'dstructs-matrix' ), + csum = require( './../lib' ), + cast = require( 'compute-cast-arrays' ); -var data = new Array( 1000 ); +var data, + mat, + out, + tmp, + i; +// ---- +// Plain arrays... +var data = new Array( 1000 ); for ( var i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random()*100 ); } @@ -12,5 +21,61 @@ data.sort( function sort( a, b ) { return a - b; }); -console.log( csum( data ) ); -// returns [...] \ No newline at end of file +out = csum( data ); +console.log( 'Arrays: %s\n', out ); + + +// ---- +// Object arrays (accessors)... +function getValue( d ) { + return d.x; +} +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; +} +out = csum( data, { + 'accessor': getValue +}); +console.log( 'Accessors: %s\n', out ); + +// ---- +// Typed arrays... +var data = new Array( 1000 ); +for ( var i = 0; i < data.length; i++ ) { + data[ i ] = Math.round( Math.random()*100 ); +} +data.sort( function sort( a, b ) { + return a - b; +}); +var data = cast( data, 'int32' ); +tmp = csum( data ); +out = ''; +for ( i = 0; i < data.length; i++ ) { + out += tmp[ i ]; + if ( i < data.length-1 ) { + out += ','; + } +} +console.log( 'Typed arrays: %s\n', out ); + + +// ---- +// Matrices... +mat = matrix( data, [100, 10], 'int32' ); +out = csum( mat ); +console.log( 'Matrix (along columns): %s\n', out.toString() ); + +out = csum( mat, { + 'dim': 1 +}); +console.log( 'Matrix (along rows): %s\n', out.toString() ); + + +// ---- +// Matrices (custom output data type)... +out = csum( mat, { + 'dtype': 'uint8' +}); +console.log( 'Matrix (along columns, %s): %s\n', out.dtype, out.toString() ); diff --git a/lib/accessor.js b/lib/accessor.js new file mode 100644 index 0000000..08886a8 --- /dev/null +++ b/lib/accessor.js @@ -0,0 +1,32 @@ +'use strict'; + +// CUMULATIVE SUM // + +/** +* FUNCTION: csum( out, arr, clbk ) +* Computes the cumulative sum of an array using an accessor. +* +* @param {Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} out - output array +* @param {Array} arr - input array +* @param {Function} accessor - accessor function for accessing array values +* @returns {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} output array +*/ +function csum( v, arr, clbk ) { + var len = arr.length, + i, + val; + + if ( len ) { + v[ 0 ] = clbk( arr[ 0 ], 0 ); + for ( i = 1; i < len; i++ ) { + val = clbk( arr[i], i ); + v[ i ] = v[ i-1 ] + val; + } + } + return v; +} // end FUNCTION csum() + + +// EXPORTS // + +module.exports = csum; diff --git a/lib/array.js b/lib/array.js new file mode 100644 index 0000000..3d8e057 --- /dev/null +++ b/lib/array.js @@ -0,0 +1,30 @@ +'use strict'; + +// CUMULATIVE SUM // + +/** +* FUNCTION: csum( out, arr ) +* Computes the cumulative sum of an array. +* +* @param {Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} out - output array +* @param {Array} arr - input array +* @returns {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} output array +*/ +function csum( v, arr ) { + var len = arr.length, + i; + + if ( len ) { + v[ 0 ] = arr[ 0 ]; + for ( i = 1; i < len; i++ ) { + v[ i ] = v[ i-1 ] + arr[ i ]; + } + } + + return v; +} // end FUNCTION csum() + + +// EXPORTS // + +module.exports = csum; diff --git a/lib/index.js b/lib/index.js index 82688e7..1d55c96 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,52 +1,97 @@ -/** -* -* COMPUTE: csum -* -* -* DESCRIPTION: -* - Computes the cumulative sum of a numeric array. -* -* -* NOTES: -* [1] -* -* -* TODO: -* [1] -* -* -* LICENSE: -* MIT -* -* Copyright (c) 2014. Athan Reines. -* -* -* AUTHOR: -* Athan Reines. kgryte@gmail.com. 2014. -* -*/ - 'use strict'; +// MODULES // + +var isArrayLike = require( 'validate.io-array-like' ), + isMatrixLike = require( 'validate.io-matrix-like' ), + ctors = require( 'compute-array-constructors' ), + matrix = require( 'dstructs-matrix' ).raw, + validate = require( './validate.js' ); + + + +// FUNCTIONS // + +var csum1 = require( './array.js' ), + csum2 = require( './accessor.js' ), + csum3 = require( './matrix.js' ); + +// CUMULATIVE SUM // + /** -* FUNCTION: csum( arr ) -* Computes the cumulative sum of a numeric array. +* FUNCTION: csum( x[, options] ) +* Computes the cumulative sum. * -* @param {Array} arr - numeric array -* @returns {Array} cumulative sum +* @param {Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} x - input value +* @param {Object} [options] - function options +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {Boolean} [options.copy=true] - boolean indicating if the function should return a new data structure +* @param {Number} [opts.dim=2] - dimension along which to compute the cumulative sum. +* @param {String} [opts.dtype="float64"] - output data type +* @returns {Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} cumulative sums */ -function csum( arr ) { - if ( !Array.isArray( arr ) ) { - throw new TypeError( 'csum()::invalid input argument. Must provide an array.' ); + +function csum( x, options ) { + + /* jshint newcap:false */ + var opts = {}, + ctor, + err, + dim, + dt, + d, + out; + + if ( arguments.length > 1 ) { + err = validate( opts, options ); + if ( err ) { + throw err; + } } - var len = arr.length, - v = new Array( len ); - v[ 0 ] = arr[ 0 ]; - for ( var i = 1; i < len; i++ ) { - v[ i ] = v[ i-1 ] + arr[ i ]; + if ( isMatrixLike( x ) ) { + dim = opts.dim; + if ( dim === undefined ) { + dim = 2; + } else if ( dim > 2 ) { + throw new RangeError( 'csum()::invalid option. Dimension option exceeds number of matrix dimensions. Option: `' + dim + '`.' ); + } + if ( opts.copy !== false ) { + dt = opts.dtype || 'float64'; + ctor = ctors( dt ); + if ( ctor === null ) { + throw new Error( 'csum()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + dt + '`.' ); + } + // Create an output matrix: + d = new ctor( x.length ); + out = matrix( d, x.shape, dt ); + } else { + out = x; + } + return csum3( out, x, dim ); } - return v; + + if ( isArrayLike( x ) ) { + if ( opts.copy === false ) { + out = x; + } + else if ( opts.dtype ) { + ctor = ctors( opts.dtype ); + if ( ctor === null ) { + throw new TypeError( 'csum()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + opts.dtype + '`.' ); + } + out = new ctor( x.length ); + } + else { + out = new Array( x.length ); + } + if ( opts.accessor ) { + return csum2( out, x, opts.accessor ); + } + return csum1( out, x ); + } + + throw new TypeError( 'csum()::invalid input argument. First argument must be either an array or a matrix. Value: `' + x + '`.' ); } // end FUNCTION csum() diff --git a/lib/matrix.js b/lib/matrix.js new file mode 100644 index 0000000..496af02 --- /dev/null +++ b/lib/matrix.js @@ -0,0 +1,54 @@ +'use strict'; + +// CUMULATIVE SUM // + +/** +* FUNCTION: csum( out, matrix[, dim] ) +* Computes the cumulative sum along a matrix dimension. +* +* @param {Matrix} out - output matirx +* @param {Matrix} mat - input matrix +* @param {Number} [dim=2] - matrix dimension along which to compute the cumulative sums. If `dim=1`, compute along matrix rows. If `dim=2`, compute along matrix columns. +* @returns {Matrix} output matrix +*/ +function csum( out, mat, dim ) { + + var M, N, + s0, s1, + o, + i, j, k, + val; + + if ( out.length !== mat.length ) { + throw new Error( 'csum()::invalid input arguments. Input and output matrices must be the same length.' ); + } + + if ( dim === 1 ) { + // Compute along the rows... + M = mat.shape[ 1 ]; + N = mat.shape[ 0 ]; + s0 = mat.strides[ 1 ]; + s1 = mat.strides[ 0 ]; + } else { + // Compute along the columns... + M = mat.shape[ 0 ]; + N = mat.shape[ 1 ]; + s0 = mat.strides[ 0 ]; + s1 = mat.strides[ 1 ]; + } + o = mat.offset; + for ( i = 0; i < M; i++ ) { + k = o + i*s0; + out.data[ k ] = mat.data[ k ]; + for ( j = 1; j < N; j++ ) { + val = mat.data[ k + j*s1 ]; + out.data[ k + j*s1 ] = out.data[ k + (j-1)*s1 ] + val; + } + } + return out; +} // end FUNCTION csum() + + +// EXPORTS // + +module.exports = csum; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..60d4eec --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,59 @@ +'use strict'; + +// MODULES // + +var isBoolean = require( 'validate.io-boolean-primitive' ), + isObject = require( 'validate.io-object' ), + isFunction = require( 'validate.io-function' ), + isString = require( 'validate.io-string-primitive' ), + isPositiveInteger = require( 'validate.io-positive-integer' ); + + +// VALIDATE // + +/** +* FUNCTION: validate( opts, options ) +* Validates function options. +* +* @param {Object} opts - destination for validated options +* @param {Object} options - function options +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {Number} [options.dim] - dimension +* @param {String} [options.dtype] - output data type +* @returns {Null|Error} null or an error +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( 'csum()::invalid input argument. Options argument must be an object. Value: `' + options + '`.' ); + } + if ( options.hasOwnProperty( 'accessor' ) ) { + opts.accessor = options.accessor; + if ( !isFunction( opts.accessor ) ) { + return new TypeError( 'csum()::invalid option. Accessor must be a function. Option: `' + opts.accessor + '`.' ); + } + } + if ( options.hasOwnProperty( 'copy' ) ) { + opts.copy = options.copy; + if ( !isBoolean( opts.copy ) ) { + return new TypeError( 'csum()::invalid option. Copy option must be a boolean primitive. Option: `' + opts.copy + '`.' ); + } + } + if ( options.hasOwnProperty( 'dim' ) ) { + opts.dim = options.dim; + if ( !isPositiveInteger( opts.dim ) ) { + return new TypeError( 'csum()::invalid option. Dimension option must be a positive integer. Option: `' + opts.dim + '`.' ); + } + } + if ( options.hasOwnProperty( 'dtype' ) ) { + opts.dtype = options.dtype; + if ( !isString( opts.dtype ) ) { + return new TypeError( 'csum()::invalid option. Data type option must be a string primitive. Option: `' + opts.dtype + '`.' ); + } + } + return null; +} // end FUNCTION validate() + + +// EXPORTS // + +module.exports = validate; diff --git a/package.json b/package.json index ec63b0f..46dd2de 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "compute-csum", "version": "1.0.1", - "description": "Computes the cumulative sum of a numeric array.", + "description": "Computes the cumulative sum.", "author": { "name": "Athan Reines", "email": "kgryte@gmail.com" @@ -10,6 +10,10 @@ { "name": "Athan Reines", "email": "kgryte@gmail.com" + }, + { + "name": "Philipp Burckhardt", + "email": "pburckhardt@outlook.com" } ], "scripts": { @@ -37,17 +41,26 @@ "bugs": { "url": "https://github.com/compute-io/csum/issues" }, - "dependencies": {}, + "dependencies": { + "compute-array-constructors": "^1.0.0", + "compute-cast-arrays": "^1.0.0", + "dstructs-matrix": "^2.0.0", + "validate.io-array": "^1.0.5", + "validate.io-array-like": "^1.0.1", + "validate.io-boolean-primitive": "^1.0.0", + "validate.io-function": "^1.0.2", + "validate.io-matrix-like": "^1.0.2", + "validate.io-object": "^1.0.4", + "validate.io-positive-integer": "^1.0.0", + "validate.io-string-primitive": "^1.0.0" + }, "devDependencies": { - "chai": "1.x.x", - "mocha": "1.x.x", + "chai": "3.x.x", + "mocha": "2.x.x", "coveralls": "^2.11.1", - "istanbul": "^0.3.0" + "istanbul": "^0.3.0", + "jshint": "2.x.x", + "jshint-stylish": "2.x.x" }, - "licenses": [ - { - "type": "MIT", - "url": "http://www.opensource.org/licenses/MIT" - } - ] + "license": "MIT" } diff --git a/test/test.accessor.js b/test/test.accessor.js new file mode 100644 index 0000000..01e94da --- /dev/null +++ b/test/test.accessor.js @@ -0,0 +1,59 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + csum = require( './../lib/accessor.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'accessor csum', function tests() { + + it( 'should export a function', function test() { + expect( csum ).to.be.a( 'function' ); + }); + + it( 'should compute the cumulative sum using an accessor', function test() { + var data, actual, expected; + + data = [ + {'x':2}, + {'x':4}, + {'x':5}, + {'x':3}, + {'x':8}, + {'x':2}, + {'x':0}, + {'x':2} + ]; + expected = [ 2, 6, 11, 14, 22, 24, 24, 26 ]; + + actual = new Array( data.length ); + actual = csum( actual, data, getValue ); + + assert.deepEqual( actual, expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should return an empty array if provided an empty array', function test() { + assert.deepEqual( csum( [], [], getValue ), [] ); + function getValue( d ) { + return d.x; + } + }); + +}); diff --git a/test/test.array.js b/test/test.array.js new file mode 100644 index 0000000..f13aa50 --- /dev/null +++ b/test/test.array.js @@ -0,0 +1,42 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + csum = require( './../lib/array.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'array csum', function tests() { + + it( 'should export a function', function test() { + expect( csum ).to.be.a( 'function' ); + }); + + it( 'should compute the cumulative sum', function test() { + var data, actual, expected; + + data = [ 4, 2, 3, 5, 1, 2, 2 ]; + actual = new Array( data.length ); + actual = csum( actual, data ); + expected = [4, 6, 9, 14, 15, 17, 19 ]; + + assert.deepEqual( actual, expected ); + }); + + it( 'should return an empty array if provided an empty array', function test() { + assert.deepEqual( csum( [], [] ), [] ); + }); + +}); diff --git a/test/test.js b/test/test.js index df45844..b19a6bd 100644 --- a/test/test.js +++ b/test/test.js @@ -1,3 +1,4 @@ +/* global describe, it, require */ 'use strict'; // MODULES // @@ -5,6 +6,9 @@ var // Expectation library: chai = require( 'chai' ), + // Matrix data structure: + matrix = require( 'dstructs-matrix' ), + // Module to be tested: csum = require( './../lib' ); @@ -23,9 +27,9 @@ describe( 'compute-csum', function tests() { expect( csum ).to.be.a( 'function' ); }); - it( 'should throw an error if provided a non-array', function test() { + it( 'should throw an error if the first argument is neither array-like or matrix-like', function test() { var values = [ - '5', + // '5', // valid as is array-like (length) 5, true, undefined, @@ -45,16 +49,182 @@ describe( 'compute-csum', function tests() { } }); + it( 'should throw an error if provided a dimension which is greater than 2 when provided a matrix', function test() { + var values = [ + '5', + 5, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( Error ); + } + function badValue( value ) { + return function() { + csum( matrix( [2,2] ), { + 'dim': value + }); + }; + } + }); + + it( 'should throw an error if provided an unrecognized/unsupported data type option', function test() { + var values = [ + 'beep', + 'boop' + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( Error ); + } + function badValue( value ) { + return function() { + csum( matrix( [2,2] ), { + 'dtype': value + }); + }; + } + }); + + it( 'should throw an error if provided an array and an unrecognized/unsupported data type option', function test() { + var values = [ + 'beep', + 'boop' + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( Error ); + } + function badValue( value ) { + return function() { + csum( [1,2,3], { + 'dtype': value + }); + }; + } + }); + it( 'should compute the cumulative sum', function test() { - var data, expected, results; + var data, actual, expected; + + data = [ 4, 2, 3, 5, 1, 2, 2 ]; + actual = csum( data ); + expected = [ 4, 6, 9, 14, 15, 17, 19 ]; + + assert.deepEqual( actual, expected ); + + // Mutate... + actual = csum( data, { + 'copy': false + }); + assert.strictEqual( actual, data ); + assert.deepEqual( data, expected ); + }); + + it( 'should compute the cumulative sum and return an array of a specific type', function test() { + var data, actual, expected; + + data = [ 1, 4, 9, 1, 2 ]; + expected = new Int8Array( [ 1, 5, 14, 15, 17 ] ); + + actual = csum( data, { + 'dtype': 'int8' + }); + assert.notEqual( actual, data ); + assert.strictEqual( actual.BYTES_PER_ELEMENT, 1 ); + assert.deepEqual( actual, expected ); + }); - data = [ 2, 4, 5, 3, 8, 2 ]; + it( 'should compute the cumulative sum using an accessor', function test() { + var data, actual, expected; + + data = [ + {'x':2}, + {'x':4}, + {'x':5}, + {'x':3}, + {'x':8}, + {'x':2} + ]; expected = [ 2, 6, 11, 14, 22, 24 ]; - results = csum( data ); + actual = csum( data, { + 'accessor': getValue + }); + + assert.deepEqual( actual, expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should compute the cumulative sum along matrix columns', function test() { + var d1, d2, data, actual, expected; + + d1 = new Int16Array( [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] ); + d2 = new Int16Array( [ 0, 1, 3, 3, 7, 12, 6, 13, 21 ] ); + expected = matrix( d2, [3,3], 'float64' ); + data = matrix( d1, [3,3], 'int16' ); + + actual = csum( data, { + 'dim': 2 + }); + + assert.deepEqual( actual.data, expected.data ); + + // Mutate... + actual = csum( data, { + 'copy': false, + 'dim': 2 + }); + assert.strictEqual( actual, data ); + assert.deepEqual( actual.data, d2 ); + }); + + it( 'should compute the cumulative sum along matrix rows', function test() { + var d1, d2, data, actual, expected; + + d1 = new Int16Array( [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] ); + d2 = new Int16Array( [ 0, 1, 2, 3, 5, 7, 9, 12, 15 ] ); + expected = matrix( d2, [3,3], 'float64' ); + data = matrix( d1, [3,3], 'int16' ); + + actual = csum( data, { + 'dim': 1 + }); + + assert.deepEqual( actual.data, expected.data ); + + // Mutate... + actual = csum( data, { + 'copy': false, + 'dim': 1 + }); + assert.strictEqual( actual, data ); + assert.deepEqual( actual.data, d2 ); + }); + + it( 'should compute the cumulative sum along matrix columns and cast to a specific data type', function test() { + var d1, d2, data, actual, expected; + + d1 = new Int16Array( [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] ); + d2 = new Int16Array( [ 0, 1, 3, 3, 7, 12, 6, 13, 21 ] ); + expected = matrix( d2, [3,3], 'int16' ); + data = matrix( d1, [3,3], 'int16' ); + + actual = csum( data, { + 'dim': 2, + 'dtype': 'int16' + }); - assert.strictEqual( results.length, expected.length ); - assert.deepEqual( results, expected ); + assert.deepEqual( actual.data, expected.data ); }); }); diff --git a/test/test.matrix.js b/test/test.matrix.js new file mode 100644 index 0000000..4e76e69 --- /dev/null +++ b/test/test.matrix.js @@ -0,0 +1,87 @@ +/* global describe, it, require, beforeEach */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Matrix data structure: + matrix = require( 'dstructs-matrix' ), + + // Module to be tested: + csum = require( './../lib/matrix.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'matrix csum', function tests() { + + var out1, out2, + mat, + d1, d2, d3; + + d1 = new Int16Array( [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] ); + d2 = new Int16Array( [ 0, 1, 3, 3, 7, 12, 6, 13, 21 ] ); + d3 = new Int16Array( [ 0, 1, 2, 3, 5, 7, 9, 12, 15 ] ); + + beforeEach( function before() { + mat = matrix( d1, [3,3], 'int16' ); + out1 = matrix( d2, [3,3], 'int16' ); + out2 = matrix( d3, [3,3], 'int16' ); + }); + + it( 'should export a function', function test() { + expect( csum ).to.be.a( 'function' ); + }); + + it( 'should throw an error if provided unequal length matrices', function test() { + expect( badValues ).to.throw( Error ); + function badValues() { + csum( matrix( [10,10] ), mat ); + } + }); + + it( 'should compute the cumulative sum along matrix columns', function test() { + var actual; + + actual = matrix( [3,3], 'int16' ); + actual = csum( actual, mat, 2 ); + + assert.deepEqual( actual.data, out1.data ); + }); + + + it( 'should compute the cumulative sum along matrix rows', function test() { + var actual; + + actual = matrix( [3,3], 'int16' ); + actual = csum( actual, mat, 1 ); + + assert.deepEqual( actual.data, out2.data ); + }); + + + it( 'should return an empty matrix if provided an empty matrix', function test() { + var out, mat, expected; + + out = matrix( [0,0] ); + expected = matrix( [0,0] ).data; + + mat = matrix( [0,10] ); + assert.deepEqual( csum( out, mat ).data, expected ); + + mat = matrix( [10,0] ); + assert.deepEqual( csum( out, mat ).data, expected ); + + mat = matrix( [0,0] ); + assert.deepEqual( csum( out, mat ).data, expected ); + }); + +}); diff --git a/test/test.validate.js b/test/test.validate.js new file mode 100644 index 0000000..180d228 --- /dev/null +++ b/test/test.validate.js @@ -0,0 +1,156 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + validate = require( './../lib/validate.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'validate', function tests() { + + it( 'should export a function', function test() { + expect( validate ).to.be.a( 'function' ); + }); + + it( 'should return an error if provided an options argument which is not an object', function test() { + var values = [ + '5', + 5, + true, + undefined, + null, + NaN, + function(){}, + [] + ]; + + for ( var i = 0; i < values.length; i++ ) { + assert.isTrue( validate( {}, values[ i ] ) instanceof TypeError ); + } + }); + + it( 'should return an error if provided an accessor which is not a function', function test() { + var values, err; + + values = [ + '5', + 5, + true, + undefined, + null, + NaN, + [], + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'accessor': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return an error if provided a copy option which is not a boolean primitive', function test() { + var values, err; + + values = [ + '5', + 5, + new Boolean( true ), + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'copy': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return an error if provided a dim option which is not a positive integer', function test() { + var values, err; + + values = [ + '5', + Math.PI, + -1, + 0, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'dim': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return an error if provided a dtype option which is not a string primitive', function test() { + var values, err; + + values = [ + 5, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'dtype': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return null if all options are valid', function test() { + var err; + + err = validate( {}, { + 'accessor': function getValue(){}, + 'copy': false, + 'dim': 2, + 'dtype': 'int32' + }); + + assert.isNull( err ); + + err = validate( {}, { + 'beep': true, // misc options + 'boop': 'bop' + }); + + assert.isNull( err ); + }); + +});