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/.jshintrc b/.jshintrc index d09f1fa..0d8844e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -68,4 +68,4 @@ "wsh": false, "yui": false, "globals": {} -} \ No newline at end of file +} diff --git a/.npmignore b/.npmignore index 7de8cda..9db298d 100644 --- a/.npmignore +++ b/.npmignore @@ -47,5 +47,6 @@ Desktop.ini # Utilities # ############# .jshintrc +.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 194e56d..f8ba9b9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ argmax === [![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 maximum value of a numeric array and returns the corresponding array indices. +> Returns the element indices corresponding to the maximum value. ## Installation @@ -16,36 +16,154 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse ## Usage -To use the module, - ``` javascript var argmax = require( 'compute-argmax' ); ``` -#### argmax( arr ) +#### argmax( x[, options] ) + +Returns the indices corresponding to the maximum value of input `x`.`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, idx; + +data = [ 2, 4, 8, 3, 8, 2 ]; +idx = argmax( data ); +// returns [ 2, 4 ] + +data = new Int8Array( data ); +idx = argmax( data ); +// returns [ 2, 4 ] +``` -Computes the maximum value of a numeric `array` and returns the corresponding `array` indices. +For non-numeric `arrays`, provide an accessor `function` for accessing numeric values ``` javascript -var data = [ 3, 2, 5, 2, 5 ]; +var arr = [ + {'x':3}, + {'x':2}, + {'x':5}, + {'x':4}, + {'x':4}, + {'x':5} +]; + +function getValue( d ) { + return d.x; +} -var idx = argmax( data ); -// returns [2,4] +var val = argmax( arr, getValue ); +// returns [ 2, 5 ] ``` +__Note__: if provided an empty `array`, the function returns `null`. + +If provided a [`matrix`](https://github.com/dstructs/matrix), the function accepts the following `option`: + +* __dim__: dimension along which to compute the maximum. Default: `2` (along the columns). + +By default, the function computes the maximum value along the columns (`dim=2`). + +``` javascript +var matrix = require( 'dstructs-matrix' ), + data, + mat, + idx, + i; + +data = new Int8Array( [ 1, 2, 3, 6, 5, 4, 7, 9, 8 ] ); +mat = matrix( data, [3,3], 'int8' ); +/* + [ 1 2 3 + 6 5 4 + 7 9 8 ] +*/ + +idx = argmax( mat ); +/* + [ [ 2 ], + [ 0 ], + [ 1 ] ] +*/ +``` + +To compute the maximum along the rows, set the `dim` option to `1`. +``` javascript +idx = argmax( mat, { + 'dim': 1 +}); +/* + [ [ 2 ], + [ 2 ], + [ 2 ] ] +*/ +``` ## Examples ``` javascript -var argmax = require( 'compute-argmax' ); +var matrix = require( 'dstructs-matrix' ), + argmax = require( 'compute-argmax' ), + util = require( 'util' ); + +var data, + mat, + idx, + i; -// Simulate some data... +// ---- +// Plain arrays... var data = new Array( 100 ); for ( var i = 0; i < data.length; i++ ) { - data[ i ] = Math.round( Math.random()*100 ); + data[ i ] = Math.random() * 100; +} +idx = argmax( data ); +console.log( 'Arrays: %d\n', idx ); + + +// ---- +// Object arrays (accessors)... +function getValue( d ) { + return d.x; +} +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; } -var idx = argmax( data ); -console.log( idx ); +idx = argmax( data, { + 'accessor': getValue +}); +console.log( 'Accessors: %d\n', idx ); + + +// ---- +// Typed arrays... +data = new Int32Array( 1000 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = Math.round( Math.random() * 100 ); +} +idx = argmax( data ); +console.log( 'Typed Arrays: \n' ); +console.log( util.inspect( idx ) ); + +// ---- +// Matrices (along rows)... +mat = matrix( data, [20,50], 'int32' ); +idx = argmax( mat, { + 'dim': 1 +}); +console.log( 'Matrix (rows): \n' ); +console.log( util.inspect( idx ) ); + +// ---- +// Matrices (along columns)... +idx = argmax( mat, { + 'dim': 2 +}); +console.log( 'Matrix (columns): \n'); +console.log( util.inspect( idx ) ); + ``` To run the example code from the top-level application directory, @@ -59,7 +177,7 @@ $ node ./examples/index.js ### 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 @@ -83,16 +201,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-argmax.svg [npm-url]: https://npmjs.org/package/compute-argmax diff --git a/examples/index.js b/examples/index.js index 5a438bd..bf14120 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,11 +1,63 @@ 'use strict'; -var argmax = require( './../lib' ); +var matrix = require( 'dstructs-matrix' ), + argmax = require( './../lib' ), + util = require( 'util' ); -// Simulate some data... +var data, + mat, + idx, + i; + +// ---- +// Plain arrays... var data = new Array( 100 ); for ( var i = 0; i < data.length; i++ ) { - data[ i ] = Math.round( Math.random()*100 ); + data[ i ] = Math.random() * 100; +} +idx = argmax( data ); +console.log( 'Arrays: %d\n', idx ); + + +// ---- +// Object arrays (accessors)... +function getValue( d ) { + return d.x; +} +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; } -var idx = argmax( data ); -console.log( idx ); +idx = argmax( data, { + 'accessor': getValue +}); +console.log( 'Accessors: %d\n', idx ); + + +// ---- +// Typed arrays... +data = new Int32Array( 1000 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = Math.round( Math.random() * 100 ); +} +idx = argmax( data ); +console.log( 'Typed Arrays: \n' ); +console.log( util.inspect( idx ) ); + +// ---- +// Matrices (along rows)... +mat = matrix( data, [20,50], 'int32' ); +idx = argmax( mat, { + 'dim': 1 +}); +console.log( 'Matrix (rows): \n' ); +console.log( util.inspect( idx ) ); + +// ---- +// Matrices (along columns)... +idx = argmax( mat, { + 'dim': 2 +}); +console.log( 'Matrix (columns): \n'); +console.log( util.inspect( idx ) ); diff --git a/lib/accessor.js b/lib/accessor.js new file mode 100644 index 0000000..4346bba --- /dev/null +++ b/lib/accessor.js @@ -0,0 +1,41 @@ +'use strict'; + +/** +* FUNCTION: max( arr, clbk ) +* Computes the maximum value of an array using an accessor. +* +* @param {Array} arr - input array +* @param {Function} [accessor] - accessor function for accessing array values +* @returns {Number[]|Null} maximum value indices +*/ +function argmax( arr, clbk ) { + var len = arr.length, + max, + i, v, + idx; + + if ( !len ) { + return null; + } + + max = clbk( arr[ 0 ], 0 ); + idx = [ 0 ]; + for ( i = 1; i < len; i++ ) { + v = clbk( arr[ i ], i ); + if ( v > max ) { + max = v; + idx.length = 0; + idx.push( i ); + } + else if ( v === max ) { + idx.push( i ); + } + } + + return idx; +} // end FUNCTION argmax() + + +// EXPORTS // + +module.exports = argmax; diff --git a/lib/array.js b/lib/array.js new file mode 100644 index 0000000..fa68e43 --- /dev/null +++ b/lib/array.js @@ -0,0 +1,38 @@ +'use strict'; + +/** +* FUNCTION: argmax( arr ) +* Computes the maximum value of an array and returns the corresponding array indices. +* +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array +* @returns {Number[]|Null} maximum value indices +*/ +function argmax( arr ) { + var len = arr.length, + max, + i, v, + idx; + + if ( !len ) { + return null; + } + max = arr[ 0 ]; + idx = [ 0 ]; + for ( i = 1; i < len; i++ ) { + v = arr[ i ]; + if ( v > max ) { + max = v; + idx.length = 0; + idx.push( i ); + } + else if ( v === max ) { + idx.push( i ); + } + } + return idx; +} // end FUNCTION argmax() + + +// EXPORTS // + +module.exports = argmax; diff --git a/lib/index.js b/lib/index.js index cda8e3c..887aeda 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,61 +1,76 @@ -/** -* -* COMPUTE: argmax -* -* -* DESCRIPTION: -* - Computes the maximum value of a numeric array and returns the corresponding array indices. -* -* -* 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' ), + validate = require( './validate.js' ); + + +// FUNCTIONS // + +var argmax1 = require( './array.js' ), + argmax2 = require( './accessor.js' ), + argmax3 = require( './matrix.js' ); + +// ARG MAX // + /** -* FUNCTION: argmax( arr ) -* Computes the maximum value of a numeric array and returns the corresponding array indices. +* FUNCTION: argmax( x[, options] ) +* Computes the maximum value of elements in x and returns the corresponding array indices. * -* @param {Array} arr - array of values -* @returns {Array} array indices +* @param {Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} x - input value +* @param {Object} [opts] - function options +* @param {Function} [opts.accessor] - accessor function for accessing array values +* @param {Number} [opts.dim=2] - dimension along which to compute the maximum +* @returns {Number[]|Array|Null} maximum value indices or null */ -function argmax( arr ) { - if ( !Array.isArray( arr ) ) { - throw new TypeError( 'argmax()::invalid input argument. Must provide an array.' ); +function argmax( x, options ) { + + /* jshint newcap:false */ + var opts = {}, + shape, + err, + len, + dim, + idx; + + if ( arguments.length > 1 ) { + err = validate( opts, options ); + if ( err ) { + throw err; + } } - var len = arr.length, - max = arr[ 0 ], - idx = [ 0 ], - val; + if ( isMatrixLike( x ) ) { + + dim = opts.dim; + if ( dim > 2 ) { + throw new RangeError( 'argmax()::invalid option. Dimension option exceeds number of matrix dimensions. Option: `' + dim + '`.' ); + } - for ( var i = 1; i < len; i++ ) { - val = arr[ i ]; - if ( val > max ) { - max = val; - idx.length = 0; - idx.push( i ); + // Determine if provided a vector... + if ( x.shape[ 0 ] === 1 || x.shape[ 1 ] === 1 ) { + // Treat as an array-like object: + return argmax1( x.data ); } - else if ( val === max ) { - idx.push( i ); + if ( dim === void 0 || dim === 2 ) { + len = x.shape[ 0 ]; + shape = [ len, 1 ]; + } else { + len = x.shape[ 1 ]; + shape = [ 1, len ]; + } + idx = []; + return argmax3( idx, x, dim ); + } + + if ( isArrayLike( x ) ) { + if ( opts.accessor ) { + return argmax2( x, opts.accessor ); } + return argmax1( x ); } - return idx; + throw new TypeError( 'argmax()::invalid input argument. First argument must be either an array or a matrix. Value: `' + x + '`.' ); } // end FUNCTION argmax() diff --git a/lib/matrix.js b/lib/matrix.js new file mode 100644 index 0000000..1cc5981 --- /dev/null +++ b/lib/matrix.js @@ -0,0 +1,60 @@ +'use strict'; + +/** +* FUNCTION: argmax( out, mat[, dim] ) +* Computes the maximum value along a matrix dimension and return the corresponding indices. +* +* @param {Array} idx - output array +* @param {Matrix} mat - input matrix +* @param {Number} [dim=2] - matrix dimension along which to compute the arg max. If `dim=1`, compute along matrix rows. If `dim=2`, compute along matrix columns. +* @returns {Array|Null} arrays of maximum value indices or null +*/ +function argmax( idx, mat, dim ) { + var max, + v, + M, N, + s0, s1, + o, + i, j, k; + + 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 ]; + } + if ( M === 0 || N === 0 ) { + return null; + } + o = mat.offset; + idx = new Array( M ); + for ( i = 0; i < M; i++ ) { + k = o + i*s0; + max = mat.data[ k ]; + idx[ i ] = [ 0 ]; + for ( j = 1; j < N; j++ ) { + v = mat.data[ k + j*s1 ]; + if ( v > max ) { + max = v; + idx[ i ].length = 0; + idx[ i ].push( j ); + } + else if ( v === max ) { + idx[ i ].push( j ); + } + } + } + return idx; +} // end FUNCTION argmax() + + +// EXPORTS // + +module.exports = argmax; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..18031a4 --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,44 @@ +'use strict'; + +// MODULES // + +var isObject = require( 'validate.io-object' ), + isFunction = require( 'validate.io-function' ), + 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 +* @returns {Null|Error} null or an error +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( 'argmax()::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( 'argmax()::invalid option. Accessor must be a function. Option: `' + opts.accessor + '`.' ); + } + } + if ( options.hasOwnProperty( 'dim' ) ) { + opts.dim = options.dim; + if ( !isPositiveInteger( opts.dim ) ) { + return new TypeError( 'argmax()::invalid option. Dimension option must be a positive integer. Option: `' + opts.dim + '`.' ); + } + } + return null; +} // end FUNCTION validate() + + +// EXPORTS // + +module.exports = validate; diff --git a/package.json b/package.json index dd0314b..8b25bc9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "compute-argmax", "version": "1.0.0", - "description": "Computes the maximum value of a numeric array and returns the corresponding array indices.", + "description": "Returns the array indices corresponding to the maximum value of an input array.", "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": { @@ -32,22 +36,32 @@ "stats", "array", "max", - "maximum" + "maximum", + "range", + "extent", + "arg", + "index", + "accessor" ], "bugs": { "url": "https://github.com/compute-io/argmax/issues" }, - "dependencies": {}, + "dependencies": { + "dstructs-matrix": "^2.0.0", + "validate.io-array-like": "^1.0.0", + "validate.io-function": "^1.0.2", + "validate.io-matrix-like": "^1.0.0", + "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", "coveralls": "^2.11.1", - "istanbul": "^0.3.0" + "istanbul": "^0.3.0", + "jshint": "2.x.x", + "jshint-stylish": "^2.0.0", + "mocha": "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..5bbadf3 --- /dev/null +++ b/test/test.accessor.js @@ -0,0 +1,55 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + argmax = require( './../lib/accessor.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'accessor arg maximum', function tests() { + + it( 'should export a function', function test() { + expect( argmax ).to.be.a( 'function' ); + }); + + it( 'should compute the maximum and return the corresponding indices using an accessor', function test() { + var data, expected; + + data = [ + {'x':2}, + {'x':4}, + {'x':5}, + {'x':3}, + {'x':8}, + {'x':2} + ]; + expected = [ 4 ]; + + assert.deepEqual( argmax( data, getValue ), expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should return null if provided an empty array', function test() { + assert.isNull( argmax( [], 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..bf116b0 --- /dev/null +++ b/test/test.array.js @@ -0,0 +1,40 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + argmax = require( './../lib/array.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'array arg maximum', function tests() { + + it( 'should export a function', function test() { + expect( argmax ).to.be.a( 'function' ); + }); + + it( 'should compute the maximum and return the corresponding indices', function test() { + var data, expected; + + data = [ 2, 4, 5, 3, 8, 2 ]; + expected = [ 4 ]; + + assert.deepEqual( argmax( data ), expected ); + }); + + it( 'should return null if provided an empty array', function test() { + assert.isNull( argmax( [] ) ); + }); + +}); diff --git a/test/test.js b/test/test.js index cacb597..8cd2770 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: argmax = require( './../lib' ); @@ -23,9 +27,9 @@ describe( 'compute-argmax', function tests() { expect( argmax ).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,14 +49,139 @@ describe( 'compute-argmax', function tests() { } }); - it( 'should compute the maximum value and return the corresponding indices', function test() { + 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() { + argmax( matrix( [2,2] ), { + 'dim': value + }); + }; + } + }); + + it( 'should compute the maximum and return the corresponding indices', function test() { + var data, expected; + + data = [ 2, 4, 5, 3, 8, 2 ]; + expected = [ 4 ]; + + assert.deepEqual( argmax( data ), expected ); + }); + + it( 'should compute the maximum of a typed array and return the corresponding indices', function test() { + var data, expected; + + data = new Int8Array( [ 2, 4, 5, 3, 8, 2 ] ); + expected = [ 4 ]; + + assert.deepEqual( argmax( data ), expected ); + }); + + it( 'should compute the maximum using an accessor function and return the corresponding indices', function test() { var data, expected, actual; - data = [ 4, 2, 8, 3, 8, 2 ]; - expected = [ 2, 4 ]; - actual = argmax( data ); + data = [ + {'x':2}, + {'x':4}, + {'x':5}, + {'x':3}, + {'x':8}, + {'x':2} + ]; + + expected = [ 4 ]; + actual = argmax( data, { + 'accessor': getValue + }); assert.deepEqual( actual, expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should compute the maximum and return the corresponding indices along a matrix dimension', function test() { + var expected, + data, + mat, + idx, + i; + + data = new Int8Array( 25 ); + for ( i = 0; i < data.length; i++ ) { + data[ i ] = i; + } + mat = matrix( data, [5,5], 'int8' ); + + // Default: + idx = argmax( mat ); + expected = [ + [ 4 ], + [ 4 ], + [ 4 ], + [ 4 ], + [ 4 ], + ]; + + assert.deepEqual( idx, expected, 'default' ); + + // Along columns: + idx = argmax( mat, { + 'dim': 2 + }); + expected = [ + [ 4 ], + [ 4 ], + [ 4 ], + [ 4 ], + [ 4 ], + ]; + + assert.deepEqual( idx, expected, 'dim: 2' ); + + // Along rows: + idx = argmax( mat, { + 'dim': 1 + }); + expected = [ + [ 4 ], + [ 4 ], + [ 4 ], + [ 4 ], + [ 4 ], + ]; + + assert.deepEqual( idx, expected, 'dim: 1' ); + }); + + it( 'should compute the maximum of 1d matrices (vectors) and return the corresponding indices', function test() { + var data, mat; + + data = [ 2, 4, 5, 3, 8, 2 ]; + + // Row vector: + mat = matrix( data, [1,6], 'int8' ); + assert.deepEqual( argmax( mat ), [ 4 ] ); + + // Column vector: + mat = matrix( data, [6,1], 'int8' ); + assert.deepEqual( argmax( mat ), [ 4 ] ); }); }); diff --git a/test/test.matrix.js b/test/test.matrix.js new file mode 100644 index 0000000..37a3ef4 --- /dev/null +++ b/test/test.matrix.js @@ -0,0 +1,119 @@ +/* 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: + argmax = require( './../lib/matrix.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'matrix arg maximum', function tests() { + + var data, + mat; + + data = new Int8Array( [ 1, 2, 3, 6, 5, 4, 7, 9, 8 ] ); + + beforeEach( function before() { + mat = matrix( data, [3,3], 'int8' ); + }); + + it( 'should export a function', function test() { + expect( argmax ).to.be.a( 'function' ); + }); + + it( 'should compute the maximum along matrix columns and return the corresponding indices', function test() { + var out, idx, expected; + + out = matrix( [3,1], 'int8' ); + + idx = argmax( out, mat ); + expected = [ + [ 2 ], + [ 0 ], + [ 1 ] + ]; + + assert.deepEqual( idx, expected ); + + idx = argmax( out, mat, 2 ); + expected = [ + [ 2 ], + [ 0 ], + [ 1 ] + ]; + + assert.deepEqual( idx, expected ); + + // Flip a matrix up-down: + mat.strides[ 0 ] *= -1; + mat.offset = mat.length + mat.strides[ 0 ]; + + idx = argmax( out, mat ); + expected = [ + [ 1 ], + [ 0 ], + [ 2 ] + ]; + + assert.deepEqual( idx, expected ); + }); + + it( 'should compute the maximum along matrix rows and return the corresponding indices', function test() { + var out, idx, expected; + + out = matrix( [1,3], 'int8' ); + + idx = argmax( out, mat, 1 ); + expected = [ + [ 2 ], + [ 2 ], + [ 2 ] + ]; + + assert.deepEqual( idx, expected ); + + // Flip a matrix left-right: + mat.strides[ 1 ] *= -1; + mat.offset = mat.strides[ 0 ] - 1; + + idx = argmax( out, mat, 1 ); + expected = [ + [ 2 ], + [ 2 ], + [ 2 ] + ]; + + assert.deepEqual( idx, expected ); + }); + + it( 'should return null if provided a matrix having one or more zero dimensions', function test() { + var out, mat; + + out = matrix( [0,0] ); + + mat = matrix( [0,10] ); + assert.isNull( argmax( out, mat ) ); + + mat = matrix( [10,0] ); + assert.isNull( argmax( out, mat ) ); + + mat = matrix( [0,0] ); + assert.isNull( argmax( out, mat ) ); + }); + +}); diff --git a/test/test.validate.js b/test/test.validate.js new file mode 100644 index 0000000..29fbc12 --- /dev/null +++ b/test/test.validate.js @@ -0,0 +1,102 @@ +/* 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 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 null if all options are valid', function test() { + var err; + + err = validate( {}, { + 'accessor': function getValue(){}, + 'dim': 2, + 'dtype': 'int32' + }); + + assert.isNull( err ); + }); + + });