diff --git a/Makefile b/Makefile index 3234fbb..c6f009c 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ ISTANBUL_HTML_REPORT_PATH ?= $(ISTANBUL_OUT)/lcov-report/index.html # JSHINT # JSHINT ?= ./node_modules/.bin/jshint -JSHINT_REPORTER ?= ./node_modules/jshint-stylish/stylish.js +JSHINT_REPORTER ?= ./node_modules/jshint-stylish diff --git a/README.md b/README.md index 542a316..9c57b36 100644 --- a/README.md +++ b/README.md @@ -20,41 +20,73 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse var divide = require( 'compute-divide' ); ``` -#### divide( arr, x[, opts] ) +#### divide( x, y[, opts] ) -Computes an element-wise division. `x` may be either an `array` of equal length or a `numeric` value. +Computes an element-wise division. `x` can be a [`number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), [`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). `y` has to be either an `array` or `matrix` of equal dimensions as `x` or a single number. The function returns either an `array` with length equal to that of the input `array`, a `matrix` with equal dimensions as input `x` or a single number. ``` javascript -var arr = [ 2, 1, 4, 2 ], - out; +var matrix = require( 'dstructs-matrix' ), + data, + mat, + out, + i; -out = divide( arr, 2 ); -// returns [ 1, 0.5, 2, 1 ] +out = divide( 6, 3 ); +// returns 2 -out = divide( arr, [ 1, 2, 8, 8 ] ); -// returns [ 2, 1, 0.5, 0.25 ] -``` +out = divide( 8, 2 ); +// returns 4 -The function accepts the following `options`: +data = [ 2, 4, 6 ]; +out = divide( data, 2 ); +// returns [ 1, 2, 3 ] -* __copy__: `boolean` indicating whether to return a new `array`. Default: `true`. -* __accessor__: accessor `function` for accessing values in object `arrays`. +data = [ 1, 2, 3 ]; +out = divide( 6, data ); +// returns [ 6, 3, 2 ] -To mutate the input `array` (e.g., when input values can be discarded or when optimizing memory usage), set the `copy` option to `false`. +data = [ 12, 6, 4 ]; +out = divide( data, [ 6, 3, 2 ] ) +// returns [ 2, 2, 2 ] -``` javascript -var arr = [ 5, 3, 8, 3, 2 ]; -var out = divide( arr, 4, { - 'copy': false -}); -// returns [ 1.25, 0.75, 0.5, 0.75, 0.5 ] +data = new Int8Array( [2,4,6] ); +out = divide( data, 2 ); +// returns Float64Array( [1,2,3] ) -console.log( arr === out ); -// returns true +data = new Int16Array( 6 ); +for ( i = 0; i < 6; i++ ) { + data[ i ] = i; +} +mat = matrix( data, [3,2], 'int16' ); +/* + [ 0 1 + 2 3 + 4 5 ] +*/ + +out = divide( mat, 2 ); +/* + [ 0 0.5 + 1 1.5 + 2 2.5 ] +*/ + +out = divide( mat, mat ); +/* + [ NaN 1 + 1 1 + 1 1 ] +*/ ``` -__Note__: mutation is the `array` equivalent of a __slash-equal__ (`/=`). +The function accepts the following `options`: + +* __accessor__: accessor `function` for accessing `array` values. +* __dtype__: output [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) or [`matrix`](https://github.com/dstructs/matrix) data type. Default: `float64`. +* __copy__: `boolean` indicating if the `function` should return a new data structure. Default: `true`. +* __path__: [deepget](https://github.com/kgryte/utils-deep-get)/[deepset](https://github.com/kgryte/utils-deep-set) key path. +* __sep__: [deepget](https://github.com/kgryte/utils-deep-get)/[deepset](https://github.com/kgryte/utils-deep-set) key path separator. Default: `'.'`. For object `arrays`, provide an accessor `function` for accessing `array` values. @@ -71,10 +103,10 @@ function getValue( d, i ) { return d[ 1 ]; } -var out = divide( data, 4, { +var out = divide( data, 2, { 'accessor': getValue }); -// returns [ 1.25, 0.75, 2, 0.75, 0.5 ] +// returns [ 2.5, 1.5, 4, 1.5, 1 ] ``` When dividing values between two object `arrays`, provide an accessor `function` which accepts `3` arguments. @@ -112,21 +144,233 @@ var out = divide( data, arr, { __Note__: `j` corresponds to the input `array` index, where `j=0` is the index for the first input `array` and `j=1` is the index for the second input `array`. +To [deepset](https://github.com/kgryte/utils-deep-set) an object `array`, provide a key path and, optionally, a key path separator. +``` javascript +var data = [ + {'x':[0,2]}, + {'x':[1,3]}, + {'x':[2,5]}, + {'x':[3,7]}, + {'x':[4,11]} +]; -## Examples +var out = divide( data, 2, 'x|1', '|' ); +/* + [ + {'x':[0,1]}, + {'x':[1,1.5]}, + {'x':[2,2.5]}, + {'x':[3,3.5}, + {'x':[4,5.5]} + ] +*/ + +var bool = ( data === out ); +// returns true +``` + +By default, when provided a [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) or [`matrix`](https://github.com/dstructs/matrix), the output data structure is `float64` in order to preserve precision. 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 -var divide = require( 'compute-divide' ); +var data, out; -var data = new Array( 100 ); -for ( var i = 0; i < data.length; i++ ) { +data = new Int8Array( [ 4, 8, 12 ] ); + +out = divide( data, 2, { + 'dtype': 'int32' +}); +// returns Int32Array( [2,4,6] ) + +// Works for plain arrays, as well... +out = divide( [ 4, 8, 12 ], 2, { + 'dtype': 'uint8' +}); +// returns Uint8Array( [2,4,6] ) +``` + +By default, the function returns a new data structure. To mutate the input data structure, set the `copy` option to `false`. + +``` javascript +var data, + bool, + mat, + out, + i; + +data = [ 4, 8, 12 ]; + +out = divide( data, 2, { + 'copy': false +}); +// returns [ 2, 4, 6 ] + +bool = ( data === out ); +// returns true + +data = new Int16Array( 6 ); +for ( i = 0; i < 6; i++ ) { + data[ i ] = i; +} +mat = matrix( data, [3,2], 'int16' ); +/* + [ 0 1 + 2 3 + 4 5 ] +*/ + +out = divide( mat, 2, { + 'copy': false +}); +/* + [ 0 0.5 + 1 1.5 + 2 2.5 ] +*/ + +bool = ( mat === out ); +// returns true +``` + +__Note__: mutation is the `array` or `matrix` equivalent of a __slash-equal__ (`/=`). + + +## Notes + +* If an element is __not__ a numeric value, the result of the division is `NaN`. + + ``` javascript + var data, out; + + out = divide( null, 1 ); + // returns NaN + + out = divide( true, 1 ); + // returns NaN + + out = divide( {'a':'b'}, 1 ); + // returns NaN + + out = divide( [ true, null, [] ], 1 ); + // returns [ NaN, NaN, NaN ] + + function getValue( d, i ) { + return d.x; + } + data = [ + {'x':true}, + {'x':[]}, + {'x':{}}, + {'x':null} + ]; + + out = divide( data, 1, { + 'accessor': getValue + }); + // returns [ NaN, NaN, NaN, NaN ] + + out = divide( data, 1, { + 'path': 'x' + }); + /* + [ + {'x':NaN}, + {'x':NaN}, + {'x':NaN, + {'x':NaN} + ] + */ + ``` + +* Be careful when providing a data structure which contains non-numeric elements and specifying an `integer` output data type, as `NaN` values are cast to `0`. + + ``` javascript + var out = divide( [ true, null, [] ], 1, { + 'dtype': 'int8' + }); + // returns Int8Array( [0,0,0] ) + ``` +* When calling the function with a numeric value as the first argument and a `matrix` or `array` as the second argument, only the `dtype` option is applicable. + + ``` javascript + // Valid: + var out = divide( 4, [ 1, 2, 3 ], { + 'dtype': 'int8' + }); + // returns Int8Array( [4,2,1] ) + + // Not valid: + var out = divide( 4, [ 1, 2, 3 ], { + 'copy': false + }); + // throws an error + ``` + +## Examples + +``` javascript +var matrix = require( 'dstructs-matrix' ), + divide = require( 'compute-divide' ); + +var data, + mat, + out, + tmp, + i; + +// Plain arrays... +data = new Array( 10 ); +for ( i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random()*100 ); } +out = divide( data, 10 ); + +// Object arrays (accessors)... +function getValue( d ) { + return d.x; +} +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; +} +out = divide( data, 10, { + 'accessor': getValue +}); + +// Deep set arrays... +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': [ i, data[ i ].x ] + }; +} +out = divide( data, 10, { + 'path': 'x/1', + 'sep': '/' +}); + +// Typed arrays... +data = new Int32Array( 10 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = Math.random() * 100; +} +tmp = divide( data, 10 ); +out = ''; +for ( i = 0; i < data.length; i++ ) { + out += tmp[ i ]; + if ( i < data.length-1 ) { + out += ','; + } +} -var out = divide( data, 10 ); +// Matrices... +mat = matrix( data, [5,2], 'int32' ); +out = divide( mat, 10 ); -console.log( out.join( '\n' ) ); +// Matrices (custom output data type)... +out = divide( mat, 10, { + 'dtype': 'uint8' +}); ``` To run the example code from the top-level application directory, @@ -167,12 +411,12 @@ $ make view-cov --- ## License -[MIT license](http://opensource.org/licenses/MIT). +[MIT license](http://opensource.org/licenses/MIT). ## Copyright -Copyright © 2015. The Compute.io Authors. +Copyright © 2014-2015. The [Compute.io](https://github.com/compute-io) Authors. [npm-image]: http://img.shields.io/npm/v/compute-divide.svg diff --git a/examples/index.js b/examples/index.js index 3a934c4..0166332 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,11 +1,83 @@ 'use strict'; -var divide = require( './../lib' ); +var matrix = require( 'dstructs-matrix' ), + divide = require( './../lib' ); -var data = new Array( 100 ); -for ( var i = 0; i < data.length; i++ ) { +var data, + mat, + out, + tmp, + i; + +// ---- +// Plain arrays... +data = new Array( 10 ); +for ( i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random()*100 ); } -var out = divide( data, 10 ); +out = divide( data, 10 ); +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 = divide( data, 10, { + 'accessor': getValue +}); +console.log( 'Accessors: %s\n', out ); + + +// ---- +// Deep set arrays... +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': [ i, data[ i ].x ] + }; +} +out = divide( data, 10, { + 'path': 'x/1', + 'sep': '/' +}); +console.log( 'Deepset:'); +console.dir( out ); +console.log( '\n' ); + + +// ---- +// Typed arrays... +data = new Int32Array( 10 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = Math.random() * 100; +} +tmp = divide( data, 10 ); +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, [5,2], 'int32' ); +out = divide( mat, 10 ); +console.log( 'Matrix: %s\n', out.toString() ); + -console.log( out.join( '\n' ) ); +// ---- +// Matrices (custom output data type)... +out = divide( mat, 10, { + 'dtype': 'uint8' +}); +console.log( 'Matrix (%s): %s\n', out.dtype, out.toString() ); diff --git a/lib/accessor.js b/lib/accessor.js new file mode 100644 index 0000000..ef10d6c --- /dev/null +++ b/lib/accessor.js @@ -0,0 +1,87 @@ +'use strict'; + +// MODULES // + +var isArrayLike = require( 'validate.io-array-like' ), + isTypedArrayLike = require( 'validate.io-typed-array-like' ), + isObject = require( 'validate.io-object' ); + + +// DIVIDE // + +/** +* FUNCTION: divide( out, arr, y, clbk ) +* Computes an element-wise division of an array using an accessor. +* +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} out - output array +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Number} y - either an array of equal length or a scalar +* @param {Function} accessor - accessor function for accessing array values +* @returns {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} output array +*/ +function divide( out, arr, y, clbk ) { + var len = arr.length, + i, + arrVal, yVal; + + if ( isTypedArrayLike( y ) ) { + if ( len !== y.length ) { + throw new Error( 'divide()::invalid input argument. Divisor array must have a length equal to that of the input array.' ); + } + for ( i = 0; i < len; i++ ) { + arrVal = clbk( arr[ i ], i, 0 ); + if ( typeof arrVal === 'number' ) { + out[ i ] = arrVal / y[ i ]; + } else { + out[ i ] = NaN; + } + } + } else if ( isArrayLike( y ) ) { + if ( len !== y.length ) { + throw new Error( 'divide()::invalid input argument. Divisor array must have a length equal to that of the input array.' ); + } + if ( !isObject( y[ 0 ] ) ) { + // Guess that y is a primitive array -> callback does not have to be applied + for ( i = 0; i < len; i++ ) { + arrVal = clbk( arr[ i ], i, 0 ); + if ( typeof y[ i ] === 'number' && typeof arrVal === 'number' ) { + out[ i ] = arrVal / y[ i ]; + } else { + out[ i ] = NaN; + } + } + } else { + // y is an object array, too -> callback is applied + for ( i = 0; i < len; i++ ) { + arrVal = clbk( arr[ i ], i, 0 ); + yVal = clbk( y[ i ], i, 1 ); + if ( typeof arrVal === 'number' && typeof yVal === 'number' ) { + out[ i ] = arrVal / yVal; + } else { + out[ i ] = NaN; + } + } + } + } else { + if ( typeof y === 'number' ) { + for ( i = 0; i < len; i++ ) { + arrVal = clbk( arr[ i ], i ); + if ( typeof arrVal === 'number' ) { + out[ i ] = arrVal / y; + } else { + out[ i ] = NaN; + } + } + } else { + for ( i = 0; i < len; i++ ) { + out[ i ] = NaN; + } + } + } + return out; +} // end FUNCTION divide() + + +// EXPORTS // + +module.exports = divide; diff --git a/lib/array.js b/lib/array.js new file mode 100644 index 0000000..e65264c --- /dev/null +++ b/lib/array.js @@ -0,0 +1,67 @@ +'use strict'; + +// MODULES // + +var isArrayLike = require( 'validate.io-array-like' ), + isTypedArrayLike = require( 'validate.io-typed-array-like' ); + + +// DIVIDE // + +/** +* FUNCTION: divide( out, arr, y ) +* Computes an element-wise division of an array. +* +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} out - output array +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Number} y - either an array of equal length or a scalar +* @returns {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} output array +*/ +function divide( out, arr, y ) { + var len = arr.length, + i; + + if ( isTypedArrayLike( y ) ) { + if ( len !== y.length ) { + throw new Error( 'divide()::invalid input argument. Divisor array must have a length equal to that of the input array.' ); + } + for ( i = 0; i < len; i++ ) { + if ( typeof arr[ i ] === 'number' ) { + out[ i ] = arr[ i ] / y[ i ]; + } else { + out[ i ] = NaN; + } + } + } else if ( isArrayLike( y ) ) { + if ( len !== y.length ) { + throw new Error( 'divide()::invalid input argument. Divisor array must have a length equal to that of the input array.' ); + } + for ( i = 0; i < len; i++ ) { + if ( typeof y[ i ] === 'number' && typeof arr[ i ] === 'number' ) { + out[ i ] = arr[ i ] / y[ i ]; + } else { + out[ i ] = NaN; + } + } + } else { + if ( typeof y === 'number' ) { + for ( i = 0; i < len; i++ ) { + if ( typeof arr[ i ] === 'number' ) { + out[ i ] = arr[ i ] / y; + } else { + out[ i ] = NaN; + } + } + } else { + for ( i = 0; i < len; i++ ) { + out[ i ] = NaN; + } + } + } + return out; +} // end FUNCTION divide() + + +// EXPORTS // + +module.exports = divide; diff --git a/lib/deepset.js b/lib/deepset.js new file mode 100644 index 0000000..f9cd0c3 --- /dev/null +++ b/lib/deepset.js @@ -0,0 +1,77 @@ +'use strict'; + +// MODULES // + +var isArrayLike = require( 'validate.io-array-like' ), + isTypedArrayLike = require( 'validate.io-typed-array-like' ), + deepSet = require( 'utils-deep-set' ).factory, + deepGet = require( 'utils-deep-get' ).factory; + + +// DIVIDE // + +/** +* FUNCTION: divide( arr, y, path[, sep] ) +* Computes an element-wise division or each element and deep sets the input array. +* +* @param {Array} arr - input array +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Number} y - either an array of equal length or a scalar +* @param {String} path - key path used when deep getting and setting +* @param {String} [sep] - key path separator +* @returns {Array} input array +*/ +function divide( x, y, path, sep ) { + var len = x.length, + opts = {}, + dget, + dset, + v, i; + + if ( arguments.length > 3 ) { + opts.sep = sep; + } + if ( len ) { + dget = deepGet( path, opts ); + dset = deepSet( path, opts ); + if ( isTypedArrayLike( y ) ) { + for ( i = 0; i < len; i++ ) { + v = dget( x[ i ] ); + if ( typeof v === 'number' ) { + dset( x[ i ], v / y[ i ] ); + } else { + dset( x[ i ], NaN ); + } + } + } else if ( isArrayLike( y ) ) { + for ( i = 0; i < len; i++ ) { + v = dget( x[ i ] ); + if ( typeof v === 'number' && typeof y[ i ] === 'number' ) { + dset( x[ i ], v / y[ i ] ); + } else { + dset( x[ i ], NaN ); + } + } + } else { + if ( typeof y === 'number' ) { + for ( i = 0; i < len; i++ ) { + v = dget( x[ i ] ); + if ( typeof v === 'number' ) { + dset( x[ i ], v / y ); + } else { + dset( x[ i ], NaN ); + } + } + } else { + for ( i = 0; i < len; i++ ) { + dset( x[ i ], NaN ); + } + } + } + } + return x; +} // end FUNCTION divide() + + +// EXPORTS // + +module.exports = divide; diff --git a/lib/index.js b/lib/index.js index 2a0dc90..6302ae0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,99 +2,146 @@ // MODULES // -var isArray = require( 'validate.io-array' ), - isObject = require( 'validate.io-object' ), - isFunction = require( 'validate.io-function' ), - isBoolean = require( 'validate.io-boolean-primitive' ), - isNumber = require( 'validate.io-number-primitive' ); +var isNumber = require( 'validate.io-number-primitive' ), + isnan = require( 'validate.io-nan' ), + isArray = require( 'validate.io-array' ), + isArrayLike = require( 'validate.io-array-like' ), + isTypedArrayLike = require( 'validate.io-typed-array-like' ), + isMatrixLike = require( 'validate.io-matrix-like' ), + ctors = require( 'compute-array-constructors' ), + matrix = require( 'dstructs-matrix' ), + validate = require( './validate.js' ); + + +// FUNCTIONS // + +var divide1 = require( './array.js' ), + divide2 = require( './accessor.js' ), + divide3 = require( './deepset.js' ), + divide4 = require( './matrix.js' ), + divide5 = require( './typedarray.js' ); + +/** +* FUNCTION: fill( n, val ) +* Creates an array of length n and fills it with the supplied value +* @param {Number} n - array length +* @param {*} val - value to fill the array with +* @returns {Array} array of length n +*/ +function fill( n, val ) { + var ret = new Array( n ); + for ( var i = 0; i < n; i++ ) { + ret[ i ] = val; + } + return ret; +} // DIVIDE // /** -* FUNCTION: divide( arr, x[, opts] ) +* FUNCTION: divide( x, y[, opts] ) * Computes an element-wise division. * -* @param {Number[]|Array} arr - input array -* @param {Number[]|Array|Number} x - either an array of equal length or a scalar +* @param {Number|Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} x - input value +* @param {Number|Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} y - either an array or matrix of equal dimension or a scalar * @param {Object} [opts] - function options -* @param {Boolean} [opts.copy=true] - boolean indicating whether to return a new array +* @param {Boolean} [opts.copy=true] - boolean indicating if the function should return a new data structure * @param {Function} [opts.accessor] - accessor function for accessing array values -* @returns {Number[]} output array +* @param {String} [opts.path] - deep get/set key path +* @param {String} [opts.sep="."] - deep get/set key path separator +* @param {String} [opts.dtype="float64"] - output data type +* @returns {Number|Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} value(s) after division */ -function divide( arr, x, opts ) { - var isArr = isArray( x ), - copy = true, - arity, - clbk, +function divide( x, y, options ) { + /* jshint newcap:false */ + var opts = {}, + ctor, + err, out, - len, - i; - - if ( !isArray( arr ) ) { - throw new TypeError( 'divide()::invalid input argument. Must provide an array. Value: `' + arr + '`.' ); - } - if ( !isArr && !isNumber( x ) ) { - throw new TypeError( 'divide()::invalid input argument. Second argument must either be an array or number primitive. Value: `' + x + '`.' ); + dt, + d; + // Handle cases when first argument is a number + if ( isNumber( x ) || isnan( x ) ) { + for ( var key in options ) { + if ( key !== 'dtype' ){ + throw new Error( 'divide()::only dtype option is applicable when first argument is not array- or matrix-like. Keys: `' + Object.keys( options ) + '`.' ); + } + } + if ( isMatrixLike( y ) ) { + // Create a matrix holding x's: + d = new Float64Array( fill( y.length, x ) ); + x = matrix( d, y.shape, 'float64' ); + return options ? divide( x, y, options ) : divide( x, y ); + } + if ( isArrayLike( y ) ) { + return options ? divide( fill( y.length, x ), y, options ) : divide( fill( y.length, x ), y ); + } + if ( !isNumber( y ) ) { + return NaN; + } + return x / y; } if ( arguments.length > 2 ) { - if ( !isObject( opts ) ) { - throw new TypeError( 'divide()::invalid input argument. Options argument must be an object. Value: `' + opts + '`.' ); + err = validate( opts, options ); + if ( err ) { + throw err; } - if ( opts.hasOwnProperty( 'copy' ) ) { - copy = opts.copy; - if ( !isBoolean( copy ) ) { - throw new TypeError( 'divide()::invalid option. Copy option must be a boolean primitive. Option: `' + copy + '`.' ); + } + if ( isMatrixLike( x ) ) { + if ( opts.copy !== false ) { + dt = opts.dtype || 'float64'; + ctor = ctors( dt ); + if ( ctor === null ) { + throw new Error( 'divide()::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; } - if ( opts.hasOwnProperty( 'accessor' ) ) { - clbk = opts.accessor; - if ( !isFunction( clbk ) ) { - throw new TypeError( 'divide()::invalid option. Accessor must be a function. Option: `' + clbk + '`.' ); + return divide4( out, x, y ); + } + if ( isTypedArrayLike( x ) ) { + if ( opts.copy === false ) { + out = x; + } else { + dt = opts.dtype || 'float64'; + ctor = ctors( dt ); + if ( ctor === null ) { + throw new Error( 'divide()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + dt + '`.' ); } - arity = clbk.length; + out = new ctor( x.length ); } + return divide5( out, x, y ); } - len = arr.length; - if ( copy ) { - out = new Array( len ); - } else { - out = arr; - } - // Case 1: x is an array - if ( isArr ) { - if ( len !== x.length ) { - throw new Error( 'divide()::invalid input argument. Array to be divideed must have a length equal to that of the input array.' ); + if ( isArrayLike( x ) ) { + // Handle deepset first... + if ( opts.path ) { + opts.sep = opts.sep || '.'; + return divide3( x, y, opts.path, opts.sep ); } - if ( arity === 3 ) { // clbk implied - for ( i = 0; i < len; i++ ) { - out[ i ] = clbk( arr[i], i, 0 ) / clbk( x[i], i, 1 ); - } + // Handle regular and accessor arrays next... + if ( opts.copy === false ) { + out = x; } - else if ( clbk ) { - for ( i = 0; i < len; i++ ) { - out[ i ] = clbk( arr[i], i ) / x[ i ]; + else if ( opts.dtype ) { + ctor = ctors( opts.dtype ); + if ( ctor === null ) { + throw new TypeError( 'divide()::invalid input argument. Unrecognized/unsupported array-like object. Provide either a plain or typed array. Value: `' + x + '`.' ); } + out = new ctor( x.length ); } else { - for ( i = 0; i < len; i++ ) { - out[ i ] = arr[ i ] / x[ i ]; - } + out = new Array( x.length ); } - } - // Case 2: accessor and scalar - else if ( clbk ) { - for ( i = 0; i < len; i++ ) { - out[ i ] = clbk( arr[i], i ) / x; - } - } - // Case 3: scalar - else { - for ( i = 0; i < len; i++ ) { - out[ i ] = arr[ i ] / x; + if ( opts.accessor ) { + return divide2( out, x, y, opts.accessor ); } + return divide1( out, x, y ); } - return out; + return NaN; } // end FUNCTION divide() diff --git a/lib/matrix.js b/lib/matrix.js new file mode 100644 index 0000000..d9d18a4 --- /dev/null +++ b/lib/matrix.js @@ -0,0 +1,49 @@ +'use strict'; + +// MODULES // + +var isMatrixLike = require( 'validate.io-matrix-like' ); + + +// DIVIDE // + +/** +* FUNCTION: divide( out, x, y ) +* Computes an element-wise division of a matrix +* +* @param {Matrix} out - output matirx +* @param {Matrix} x - input matrix +* @param {Matrix|Number} y - either a matrix of equal dimensions or a scalar +* @returns {Matrix} output matrix +*/ +function divide( out, x, y ) { + var len = x.length, + i, j, + M, N; + + if ( out.length !== len ) { + throw new Error( 'divide()::invalid input arguments. Input and output matrices must be the same length.' ); + } + if ( isMatrixLike( y ) ) { + M = x.shape[0]; + N = x.shape[1]; + if ( M !== x.shape[0] || N !== y.shape[1] ) { + throw new Error( 'divide()::invalid input arguments. Divisor matrix must have the same number of rows and columns as the input matrix.' ); + } + for ( i = 0; i < M; i++ ) { + for ( j = 0; j < N; j++ ) { + out.set( i, j, x.get( i, j ) / y.get( i, j ) ); + } + } + } else { + for ( i = 0; i < len; i++ ) { + out.data[ i ] = x.data[ i ] / y; + } + } + return out; +} // end FUNCTION divide() + + +// EXPORTS // + +module.exports = divide; diff --git a/lib/typedarray.js b/lib/typedarray.js new file mode 100644 index 0000000..3165cb7 --- /dev/null +++ b/lib/typedarray.js @@ -0,0 +1,59 @@ +'use strict'; + +// MODULES // + +var isArrayLike = require( 'validate.io-array-like' ), + isTypedArrayLike = require( 'validate.io-typed-array-like' ); + + +// DIVIDE // + +/** +* FUNCTION: divide( out, arr, y ) +* Computes an element-wise division of a typed array. +* +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} out - output array +* @param {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Number} y - either an array of equal length or a scalar +* @returns {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} output array +*/ +function divide( out, arr, y ) { + var len = arr.length, + i; + + if ( isTypedArrayLike( y ) ) { + if ( len !== y.length ) { + throw new Error( 'divide()::invalid input argument. Divisor array must have a length equal to that of the input array.' ); + } + for ( i = 0; i < len; i++ ) { + out[ i ] = arr[ i ] / y[ i ]; + } + } else if ( isArrayLike( y ) ) { + if ( len !== y.length ) { + throw new Error( 'divide()::invalid input argument. Divisor array must have a length equal to that of the input array.' ); + } + for ( i = 0; i < len; i++ ) { + if ( typeof y[ i ] === 'number' ) { + out[ i ] = arr[ i ] / y[ i ]; + } else { + out[ i ] = NaN; + } + } + } else { + if ( typeof y === 'number' ) { + for ( i = 0; i < len; i++ ) { + out[ i ] = arr[ i ] / y; + } + } else { + for ( i = 0; i < len; i++ ) { + out[ i ] = NaN; + } + } + } + return out; +} // end FUNCTION divide() + + +// EXPORTS // + +module.exports = divide; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..f3f9ff0 --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,66 @@ +'use strict'; + +// MODULES // + +var isObject = require( 'validate.io-object' ), + isBoolean = require( 'validate.io-boolean-primitive' ), + isFunction = require( 'validate.io-function' ), + isString = require( 'validate.io-string-primitive' ); + + +// VALIDATE // + +/** +* FUNCTION: validate( opts, options ) +* Validates function options. +* +* @param {Object} opts - destination for validated options +* @param {Object} options - function options +* @param {Boolean} [options.copy=true] - boolean indicating if the function should return a new data structure +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {String} [options.sep] - deep get/set key path separator +* @param {String} [options.path] - deep get/set key path +* @param {String} [options.dtype] - output data type +* @returns {Null|Error} null or an error +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( 'divide()::invalid input argument. Options argument must be an object. Value: `' + options + '`.' ); + } + if ( options.hasOwnProperty( 'copy' ) ) { + opts.copy = options.copy; + if ( !isBoolean( opts.copy ) ) { + return new TypeError( 'divide()::invalid option. Copy option must be a boolean primitive. Option: `' + opts.copy + '`.' ); + } + } + if ( options.hasOwnProperty( 'accessor' ) ) { + opts.accessor = options.accessor; + if ( !isFunction( opts.accessor ) ) { + return new TypeError( 'divide()::invalid option. Accessor must be a function. Option: `' + opts.accessor + '`.' ); + } + } + if ( options.hasOwnProperty( 'path' ) ) { + opts.path = options.path; + if ( !isString( opts.path ) ) { + return new TypeError( 'divide()::invalid option. Key path option must be a string primitive. Option: `' + opts.path + '`.' ); + } + } + if ( options.hasOwnProperty( 'sep' ) ) { + opts.sep = options.sep; + if ( !isString( opts.sep ) ) { + return new TypeError( 'divide()::invalid option. Separator option must be a string primitive. Option: `' + opts.sep + '`.' ); + } + } + if ( options.hasOwnProperty( 'dtype' ) ) { + opts.dtype = options.dtype; + if ( !isString( opts.dtype ) ) { + return new TypeError( 'divide()::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 f1c1022..c506ca7 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ { "name": "Athan Reines", "email": "kgryte@gmail.com" + }, + { + "name": "Philipp Burckhardt", + "email": "pburckhardt@outlook.com" } ], "scripts": { @@ -39,24 +43,28 @@ "url": "https://github.com/compute-io/divide/issues" }, "dependencies": { - "validate.io-array": "^1.0.5", + "compute-array-constructors": "^1.0.0", + "dstructs-matrix": "^2.0.0", + "utils-deep-get": "^1.0.0", + "utils-deep-set": "^1.0.1", + "validate.io-array": "^1.0.6", + "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-nan": "^1.0.3", "validate.io-number-primitive": "^1.0.0", - "validate.io-object": "^1.0.3" + "validate.io-object": "^1.0.4", + "validate.io-string-primitive": "^1.0.0", + "validate.io-typed-array-like": "^1.0.0" }, "devDependencies": { - "chai": "2.x.x", + "chai": "3.x.x", "coveralls": "^2.11.1", "istanbul": "^0.3.0", "jshint": "2.x.x", - "jshint-stylish": "^1.0.0", + "jshint-stylish": "2.x.x", "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..cfeb14e --- /dev/null +++ b/test/test.accessor.js @@ -0,0 +1,216 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + divide = require( './../lib/accessor.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'accessor divide', function tests() { + + it( 'should export a function', function test() { + expect( divide ).to.be.a( 'function' ); + }); + + it( 'should perform an element-wise division by a scalar using an accessor', function test() { + var data, actual, expected; + + data = [ + {'x':0}, + {'x':2}, + {'x':4}, + {'x':6} + ]; + actual = new Array( data.length ); + actual = divide( actual, data, 2, getValue ); + + expected = [ + 0, + 1, + 2, + 3 + ]; + + assert.deepEqual( actual, expected ); + + function getValue( d ) { + return d.x; + } + + }); + + it( 'should perform an element-wise division of an array using an accessor', function test() { + var data, actual, expected, y; + + data = [ + {'x':0}, + {'x':1}, + {'x':2}, + {'x':3} + ]; + + y = [ + 0, + 1, + 2, + 3 + ]; + + actual = new Array( data.length ); + actual = divide( actual, data, y, getValue ); + + expected = [ + NaN, + 1, + 1, + 1 + ]; + + assert.deepEqual( actual, expected ); + + function getValue( d, i ) { + return d.x; + } + + }); + + it( 'should perform an element-wise multiplication of another object array using an accessor', function test() { + var data, actual, expected, y; + + data = [ + {'x':0}, + {'x':1}, + {'x':2}, + {'x':3} + ]; + + y = [ + {'y':0}, + {'y':1}, + {'y':2}, + {'y':3} + ]; + + actual = new Array( data.length ); + actual = divide( actual, data, y, getValue ); + + expected = [ + NaN, + 1, + 1, + 1 + ]; + + assert.deepEqual( actual, expected ); + + function getValue( d, i, j ) { + if ( j === 0 ) { + return d.x; + } else { + return d.y; + } + } + + }); + + it( 'should return empty array if provided an empty array', function test() { + assert.deepEqual( divide( [], [], 1, getValue ), [] ); + function getValue( d ) { + return d.x; + } + }); + + it( 'should handle non-numeric values by setting the element to NaN', function test() { + var data, actual, expected, y; + + data = [ + {'x':1}, + {'x':null}, + {'x':3} + ]; + actual = new Array( data.length ); + actual = divide( actual, data, 1, getValue ); + + expected = [ 1, NaN, 3 ]; + + assert.deepEqual( actual, expected ); + + // single non-numeric value + y = false; + actual = new Array( data.length ); + actual = divide( actual, data, y, getValue ); + expected = [ NaN, NaN, NaN ]; + + assert.deepEqual( actual, expected ); + + // numeric array + y = [ 1, 2, 3 ]; + actual = new Array( data.length ); + actual = divide( actual, data, y, getValue ); + expected = [ 1, NaN, 1 ]; + + // typed array + y = new Int32Array( [1,2,3] ); + actual = new Array( data.length ); + actual = divide( actual, data, y, getValue ); + expected = [ 1, NaN, 1 ]; + + assert.deepEqual( actual, expected ); + + // object array + y = [ + {'y':1}, + {'y':2}, + {'y':3} + ]; + actual = new Array( data.length ); + actual = divide( actual, data, y, getValue2 ); + expected = [ 1, NaN, 1 ]; + + function getValue( d, i ) { + return d.x; + } + + function getValue2( d, i, j ) { + if ( j === 0 ) { + return d.x; + } else { + return d.y; + } + } + + }); + + it( 'should throw an error if dividing by a matrix which is not of equal length to the input array', function test() { + expect( foo ).to.throw( Error ); + function foo() { + divide( [], [1,2], [1,2,3], getValue ); + } + function getValue( d ) { + return d; + } + }); + + it( 'should throw an error if dividing by a typed array which is not of equal length to the input array', function test() { + expect( foo ).to.throw( Error ); + function foo() { + divide( [], [1,2], new Int32Array( [1,2,3] ), getValue ); + } + function getValue( d ) { + return d; + } + }); + +}); diff --git a/test/test.array.js b/test/test.array.js new file mode 100644 index 0000000..9af51d4 --- /dev/null +++ b/test/test.array.js @@ -0,0 +1,161 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + divide = require( './../lib/array.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'array divide', function tests() { + + it( 'should export a function', function test() { + expect( divide).to.be.a( 'function' ); + }); + + it( 'should divide an array by a scalar', function test() { + var data, actual, expected; + + data = [ + 2, + 4, + 6, + 8, + 10 + ]; + actual = new Array( data.length ); + + actual = divide( actual, data, 2 ); + + expected = [ + 1, + 2, + 3, + 4, + 5 + ]; + + assert.deepEqual( actual, expected ); + + // Typed arrays... + data = new Int32Array( data ); + actual = new Int32Array( data.length ); + + actual = divide( actual, data, 2 ); + expected = new Int32Array( expected ); + + assert.deepEqual( actual, expected ); + }); + + it( 'should divide an array element-wise by another array', function test() { + var data, actual, expected, y; + + data = [ + 1, + 2, + 3, + 4, + 5 + ]; + + y = [ + 1, + 2, + 3, + 4, + 5 + ]; + actual = new Array( data.length ); + + actual = divide( actual, data, y ); + + expected = [ + 1, + 1, + 1, + 1, + 1 + ]; + + assert.deepEqual( actual, expected ); + + // Typed arrays... + data = new Int32Array( data ); + actual = new Int32Array( data.length ); + + actual = divide( actual, data, y ); + expected = new Int32Array( expected ); + + assert.deepEqual( actual, expected ); + + // y being a typed array + y = new Int32Array( y ); + actual = divide( actual, data, y ); + assert.deepEqual( actual, expected ); + }); + + it( 'should return an empty array if provided an empty array', function test() { + assert.deepEqual( divide( [], [], 1 ), [] ); + assert.deepEqual( divide( new Int8Array(), new Int8Array(), 1 ), new Int8Array() ); + }); + + it( 'should handle non-numeric values by setting the element to NaN', function test() { + var data, actual, expected, y; + + data = [ true, null, [], {} ]; + actual = new Array( data.length ); + actual = divide( actual, data, 1 ); + + expected = [ NaN, NaN, NaN, NaN ]; + + assert.deepEqual( actual, expected ); + + actual = new Array( data.length ); + y = [ 1, 2, 3, 4 ]; + actual = divide( actual, data, y ); + + expected = [ NaN, NaN, NaN, NaN ]; + + assert.deepEqual( actual, expected ); + + data = [ 1, 2, 3 ]; + y = null; + actual = new Array( data.length ); + actual = divide( actual, data, y ); + expected = [ NaN, NaN, NaN ]; + + assert.deepEqual( actual, expected ); + + data = [ 1, null, 3 ]; + y = new Int32Array( [1,2,3] ); + actual = new Array( data.length ); + actual = divide( actual, data, y ); + expected = [ 1, NaN, 1 ]; + + assert.deepEqual( actual, expected ); + }); + + it( 'should throw an error if provided an array to be divided which is not of equal length to the input array', function test() { + expect( foo ).to.throw( Error ); + function foo() { + divide( [], [1,2], [1,2,3] ); + } + + expect( foo2 ).to.throw( Error ); + function foo2() { + divide( [], [1,2], new Int32Array( [1,2,3] ) ); + } + }); + +}); diff --git a/test/test.deepset.js b/test/test.deepset.js new file mode 100644 index 0000000..4f7f429 --- /dev/null +++ b/test/test.deepset.js @@ -0,0 +1,188 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + divide = require( './../lib/deepset.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'deepset divide', function tests() { + + it( 'should export a function', function test() { + expect( divide ).to.be.a( 'function' ); + }); + + it( 'should perform an element-wise division using a scalar and deep set', function test() { + var data, actual, expected; + + data = [ + {'x':0}, + {'x':2}, + {'x':4}, + {'x':6} + ]; + + actual = divide( data, 2, 'x' ); + + expected = [ + {'x':0}, + {'x':1}, + {'x':2}, + {'x':3} + ]; + + assert.strictEqual( data, actual ); + assert.deepEqual( data, expected); + + // Custom separator... + data = [ + {'x':[9,0]}, + {'x':[9,2]}, + {'x':[9,4]}, + {'x':[9,6]} + ]; + + data = divide( data, 2, 'x/1', '/' ); + expected = [ + {'x':[9,0]}, + {'x':[9,1]}, + {'x':[9,2]}, + {'x':[9,3]} + ]; + + assert.deepEqual( data, expected, 'custom separator' ); + }); + + it( 'should perform an element-wise division using an array and deep set', function test() { + var data, actual, expected, y; + + data = [ + {'x':0}, + {'x':1}, + {'x':2}, + {'x':3} + ]; + + y = [ 0, 1, 2, 3 ]; + + actual = divide( data, y, 'x' ); + + expected = [ + {'x':NaN}, + {'x':1}, + {'x':1}, + {'x':1} + ]; + + assert.strictEqual( data, actual ); + assert.deepEqual( data, expected); + + // Custom separator... + data = [ + {'x':[9,0]}, + {'x':[9,1]}, + {'x':[9,2]}, + {'x':[9,3]} + ]; + + data = divide( data, y, 'x/1', '/' ); + expected = [ + {'x':[9,NaN]}, + {'x':[9,1]}, + {'x':[9,1]}, + {'x':[9,1]} + ]; + + assert.deepEqual( data, expected, 'custom separator' ); + }); + + it( 'should return an empty array if provided an empty array', function test() { + var arr = []; + assert.deepEqual( divide( arr, 1, 'x' ), [] ); + assert.deepEqual( divide( arr, 1, 'x', '/' ), [] ); + }); + + it( 'should handle non-numeric values by setting the element to NaN', function test() { + var data, actual, expected, y; + + + // dividing by a non-numeric value + data = [ + {'x':[9,null]}, + {'x':[9,1]}, + {'x':[9,true]}, + {'x':[9,3]} + ]; + actual = divide( data, null, 'x.1' ); + expected = [ + {'x':[9,NaN]}, + {'x':[9,NaN]}, + {'x':[9,NaN]}, + {'x':[9,NaN]} + ]; + assert.deepEqual( data, expected ); + + // dividing by a scalar + data = [ + {'x':[9,null]}, + {'x':[9,1]}, + {'x':[9,true]}, + {'x':[9,3]} + ]; + actual = divide( data, 1, 'x.1' ); + expected = [ + {'x':[9,NaN]}, + {'x':[9,1]}, + {'x':[9,NaN]}, + {'x':[9,3]} + ]; + assert.deepEqual( data, expected ); + + // dividing by an array + data = [ + {'x':[9,null]}, + {'x':[9,1]}, + {'x':[9,true]}, + {'x':[9,3]} + ]; + y = [ 0, 1, 2, 3]; + actual = divide( data, y, 'x.1' ); + expected = [ + {'x':[9,NaN]}, + {'x':[9,1]}, + {'x':[9,NaN]}, + {'x':[9,1]} + ]; + assert.deepEqual( data, expected ); + + // dividing by a typed array + data = [ + {'x':[9,null]}, + {'x':[9,1]}, + {'x':[9,true]}, + {'x':[9,3]} + ]; + y = new Int32Array( [0,1,2,3] ); + actual = divide( data, y, 'x.1' ); + expected = [ + {'x':[9,NaN]}, + {'x':[9,1]}, + {'x':[9,NaN]}, + {'x':[9,1]} + ]; + assert.deepEqual( data, expected ); + }); + +}); diff --git a/test/test.js b/test/test.js index 3174c1c..2c872aa 100644 --- a/test/test.js +++ b/test/test.js @@ -6,6 +6,12 @@ var // Expectation library: chai = require( 'chai' ), + // Matrix data structure: + matrix = require( 'dstructs-matrix' ), + + // Validate if a value is NaN: + isnan = require( 'validate.io-nan' ), + // Module to be tested: divide = require( './../lib' ); @@ -24,243 +30,536 @@ describe( 'compute-divide', function tests() { expect( divide ).to.be.a( 'function' ); }); - it( 'should throw an error if not provided an array', function test() { + it( 'should throw an error if provided an invalid option', function test() { var values = [ '5', 5, - null, + true, undefined, + null, NaN, - true, - {}, - function(){} + [], + {} ]; for ( var i = 0; i < values.length; i++ ) { expect( badValue( values[i] ) ).to.throw( TypeError ); } - function badValue( value ) { return function() { - divide( value, 10 ); + divide( [1,2,3], 1, { + 'accessor': value + }); }; } }); - it( 'should throw an error if provided a second argument which is not an array or number primitive', function test() { + it( 'should throw an error if provided an array and an unrecognized/unsupported data type option', function test() { var values = [ - new Number( 5 ), - '5', - null, - undefined, - NaN, - true, - {}, - function(){} + 'beep', + 'boop' ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[i] ) ).to.throw( Error ); } - function badValue( value ) { return function() { - divide( [1,2,3], value ); + divide( [1,2,3], 1, { + 'dtype': value + }); }; } }); - it( 'should throw an error if provided an options argument which is not an object', function test() { + it( 'should throw an error if provided a typed-array and an unrecognized/unsupported data type option', function test() { var values = [ - '5', - 5, - null, - undefined, - NaN, - true, - [], - function(){} + 'beep', + 'boop' ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[i] ) ).to.throw( Error ); } - function badValue( value ) { return function() { - divide( [1,2,3], 10, value ); + divide( new Int8Array([1,2,3]), 1, { + 'dtype': value + }); }; } }); - it( 'should throw an error if provided a copy option which is not a boolean primitive', function test() { + it( 'should throw an error if provided a matrix and an unrecognized/unsupported data type option', function test() { var values = [ - '5', - 5, - null, - undefined, - NaN, - new Boolean( true ), - {}, - [], - function(){} + 'beep', + 'boop' ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[i] ) ).to.throw( Error ); } - function badValue( value ) { return function() { - divide( [1,2,3], 10, { - 'copy': value + divide( matrix( [2,2] ), 1, { + 'dtype': value }); }; } }); - it( 'should throw an error if provided an accessor option which is not a function', function test() { + it( 'should throw an error if provided a number as the first argument and an not applicable option', function test() { var values = [ - '5', - 5, - null, - undefined, - NaN, - {}, - [] + {'accessor': function getValue( d ) { return d; } }, + {'copy': false}, + {'path': 'x'}, ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[i] ) ).to.throw( Error ); } - function badValue( value ) { return function() { - divide( [1,2,3], 10, { - 'accessor': value - }); + divide( 12, [1,2,3], value ); }; } }); - it( 'should throw an error if provided an array as a second argument which is not of equal length to the input array', function test() { - expect( foo ).to.throw( Error ); - function foo() { - divide( [1,2], [1,2,3] ); + it( 'should return NaN if the first argument is neither a number, array-like, or matrix-like', function test() { + var values = [ + // '5', // valid as is array-like (length) + true, + undefined, + null, + NaN, + function(){}, + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + assert.isTrue( isnan( divide( values[ i ], 1 ) ) ); + } + }); + + + it( 'should return NaN if the first argument is a number and the second argument is neither numberic, array-like, or matrix-like', function test() { + var values = [ + // '5', // valid as is array-like (length) + true, + undefined, + null, + NaN, + function(){}, + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + assert.isTrue( isnan( divide( 8, values[ i ] ) ) ); } }); - it( 'should not mutate an input array by default', function test() { - var data, actual; + it( 'should divide two numbers', function test() { + assert.strictEqual( divide( 3, 2 ), 1.5 ); + assert.strictEqual( divide( 20, 10 ), 2 ); + }); + + it( 'should calculate the division of a scalar by an array', function test() { + var data, actual, expected; + data = [ 1, 2 ]; + actual = divide( 4, data ); + expected = [ 4, 2 ]; + assert.deepEqual( actual, expected ); + }); + + it( 'should calculate the division of a scalar by an array and cast result to a different dtype', function test() { + var data, actual, expected; + data = [ 1, 2 ]; + actual = divide( 10, data, { + 'dtype':'int32' + }); + expected = new Int32Array( [10,5] ); + assert.deepEqual( actual, expected ); + }); + + it( 'should calculate the division of a scalar by a matrix', function test() { + var data, actual, expected; + data = matrix( new Int8Array( [ 1,2,3,4 ] ), [2,2] ); + actual = divide( 12, data ); + expected = matrix( new Float64Array( [12,6,4,3] ), [2,2] ); + + assert.deepEqual( actual.data, expected.data ); + }); - data = [ 4, 5, 3, 6, 8 ]; - actual = divide( data, 4 ); + it( 'should calculate the division of a scalar by a matrix and cast to a different dtype', function test() { + var data, actual, expected; + data = matrix( new Int8Array( [ 1,2,3,4 ] ), [2,2] ); + actual = divide( 12, data, { + 'dtype': 'int32' + }); + expected = matrix( new Int32Array( [12,6,4,3] ), [2,2] ); - assert.ok( data !== actual ); + assert.strictEqual( actual.dtype, 'int32' ); + assert.deepEqual( actual.data, expected.data ); }); - it( 'should perform element-wise division', function test() { - var data, expected, actual; + it( 'should perform an element-wise division when provided a plain array and a scalar', function test() { + var data, actual, expected; - // Scalar: - data = [ 5, 2, 4, 1, 2 ]; + data = [ 0, 1, 2, 3 ]; + expected = [ + 0, + 2, + 4, + 6 + ]; - actual = divide( data, 4 ); - expected = [ 1.25, 0.5, 1, 0.25, 0.5 ]; + actual = divide( data, 0.5 ); + assert.notEqual( actual, data ); assert.deepEqual( actual, expected ); - // Array of numeric values: - data = [ 5, 2, 4, 1, 2 ]; + // Mutate... + actual = divide( data, 0.5, { + 'copy': false + }); + assert.strictEqual( actual, data ); + + assert.deepEqual( data, expected ); + + }); + + it( 'should perform an element-wise division when provided a plain array and another array', function test() { + var data, actual, expected; + + data = [ 0, 1, 2, 3 ]; + expected = [ + NaN, + 1, + 1, + 1 + ]; - actual = divide( data, [ 5, 2, 4, 1, 1 ] ); - expected = [ 1, 1, 1, 1, 2 ]; + actual = divide( data, data ); + assert.notEqual( actual, data ); assert.deepEqual( actual, expected ); + + // Mutate... + actual = divide( data, data, { + 'copy': false + }); + assert.strictEqual( actual, data ); + + assert.deepEqual( data, expected ); + }); - it( 'should mutate an input array if the `copy` option is `false`', function test() { - var data, expected, actual; + it( 'should perform an element-wise division when provided a typed array and a scalar', function test() { + var data, actual, expected; - data = [ 4, 5, 3, 6, 8 ]; + data = new Int8Array( [ 0, 1, 2, 3 ] ); - actual = divide( data, 4, { + expected = new Float64Array( [ + 0, + 0.5, + 1, + 1.5 + ]); + + actual = divide( data, 2 ); + assert.notEqual( actual, data ); + + assert.deepEqual( actual, expected ); + + // Mutate: + actual = divide( data, 2, { 'copy': false }); - expected = [ 1, 1.25, 0.75, 1.5, 2 ]; + assert.strictEqual( actual, data ); + expected = new Int8Array( [ 0, 0, 1, 1 ] ); - assert.ok( data === actual ); + assert.deepEqual( data, expected ); + }); + + it( 'should perform an element-wise division when provided a typed array and another typed array', function test() { + var data, actual, expected; + + data = new Int8Array( [ 3, 6, 9, 12 ] ); + + expected = new Float64Array( [ + 1, + 1, + 1, + 1 + ]); + + actual = divide( data, data ); + assert.notEqual( actual, data ); assert.deepEqual( actual, expected ); + + // Mutate: + + actual = divide( data, data, { + 'copy': false + }); + expected = new Int8Array( [ 1, 1, 1, 1 ] ); + assert.strictEqual( actual, data ); + + assert.deepEqual( data, expected ); }); - it( 'should perform element-wise division using an accessor', function test() { - var data, expected, actual; + it( 'should perform an element-wise division and return an array of a specific type', function test() { + var data, actual, expected; + + data = [ 0, 4, 8, 12 ]; + expected = new Int8Array( [ 0, 1, 2, 3 ] ); + + actual = divide( data, 4, { + 'dtype': 'int8' + }); + assert.notEqual( actual, data ); + assert.strictEqual( actual.BYTES_PER_ELEMENT, 1 ); + assert.deepEqual( actual, expected ); + }); + + it( 'should perform an element-wise division with a scalar using an accessor', function test() { + var data, actual, expected; data = [ - {'x':5}, - {'x':2}, - {'x':4}, - {'x':1}, - {'x':2} + [3,0], + [4,1], + [5,2], + [6,3] ]; - expected = [ 1.25, 0.5, 1, 0.25, 0.5 ]; - actual = divide( data, 4, { + expected = [ + 0, + 0.5, + 1, + 1.5 + ]; + + actual = divide( data, 2, { 'accessor': getValue }); + assert.notEqual( actual, data ); + assert.deepEqual( actual, expected ); + // Mutate: + actual = divide( data, 2, { + 'accessor': getValue, + 'copy': false + }); + assert.strictEqual( actual, data ); + + assert.deepEqual( data, expected ); + function getValue( d ) { - return d.x; + return d[ 1 ]; } }); - it( 'should perform element-wise division when provided an array and using an accessor', function test() { - var data, arr, expected, actual; + it( 'should perform an element-wise division two object arrays using an accessor', function test() { + var data, actual, expected, y; data = [ - {'x':5}, - {'x':2}, - {'x':4}, + {'x':0}, {'x':1}, - {'x':2} + {'x':2}, + {'x':3} ]; - // One array accessed... - arr = [ 5, 2, 4, 1, 1 ]; + y = [ + {'y':0}, + {'y':1}, + {'y':2}, + {'y':3} + ]; - actual = divide( data, arr, { - 'accessor': getValue1 + actual = divide( data, y, { + 'accessor': getValue }); - expected = [ 1, 1, 1, 1, 2 ]; + + expected = [ + NaN, + 1, + 1, + 1 + ]; assert.deepEqual( actual, expected ); + function getValue( d, i, j ) { + if ( j === 0 ) { + + return d.x; + } else { + return d.y; + } + } + + }); + + it( 'should perform an element-wise division with a scalar and deep set', function test() { + var data, actual, expected, i; - // Both arrays are accessed... - arr = [ - [0,5], - [1,2], - [2,4], - [3,1], - [4,1] + data = [ + {'x':[3,0]}, + {'x':[4,3]}, + {'x':[5,6]}, + {'x':[6,9]} + ]; + expected = [ + {'x':[3,0]}, + {'x':[4,1]}, + {'x':[5,2]}, + {'x':[6,3]} ]; - actual = divide( data, arr, { - 'accessor': getValue2 + actual = divide( data, 3, { + 'path': 'x.1' }); - expected = [ 1, 1, 1, 1, 2 ]; - assert.deepEqual( actual, expected ); + assert.strictEqual( actual, data ); - function getValue1( d ) { - return d.x; + for ( i = 0; i < data.length; i++ ) { + assert.closeTo( actual[ i ].x[ 1 ], expected[ i ].x[ 1 ], 1e-6 ); } - function getValue2( d, i, j ) { - if ( j === 0 ) { - return d.x; - } - return d[ 1 ]; + + // Specify a path with a custom separator... + data = [ + {'x':[3,0]}, + {'x':[4,3]}, + {'x':[5,6]}, + {'x':[6,9]} + ]; + actual = divide( data, 3, { + 'path': 'x/1', + 'sep': '/' + }); + assert.strictEqual( actual, data ); + + for ( i = 0; i < data.length; i++ ) { + assert.closeTo( actual[ i ].x[ 1 ], expected[ i ].x[ 1 ], 1e-6 ); + } + }); + + it( 'should perform an element-wise division using an array and deep set', function test() { + var data, actual, expected, y; + + data = [ + {'x':6}, + {'x':3}, + {'x':2}, + {'x':1} + ]; + + y = [ 6, 3, 2, 1 ]; + + actual = divide( data, y, { + path: 'x' + }); + + expected = [ + {'x':1}, + {'x':1}, + {'x':1}, + {'x':1} + ]; + + assert.strictEqual( data, actual ); + assert.deepEqual( data, expected); + + // Custom separator... + data = [ + {'x':[9,6]}, + {'x':[9,3]}, + {'x':[9,2]}, + {'x':[9,1]} + ]; + + data = divide( data, y, { + 'path': 'x/1', + 'sep': '/' + }); + expected = [ + {'x':[9,1]}, + {'x':[9,1]}, + {'x':[9,1]}, + {'x':[9,1]} + ]; + + assert.deepEqual( data, expected, 'custom separator' ); + }); + + it( 'should perform an element-wise division when provided a matrix', function test() { + var mat, + out, + d1, + d2, + d3, + i; + + d1 = new Int32Array( 100 ); + d2 = new Int32Array( 100 ); + d3 = new Int32Array( 100 ); + for ( i = 0; i < d1.length; i++ ) { + d1[ i ] = ( i + 1 ); + d2[ i ] = ( i + 1) / ( i + 1); + d3[ i ] = ( i + 1) / 2; + } + + // Divide matrix by scalar + mat = matrix( d1, [10,10], 'int32' ); + out = divide( mat, 2, { + 'dtype': 'int32' + }); + + assert.deepEqual( out.data, d3 ); + + // Divide two matrices element-wise + mat = matrix( d1, [10,10], 'int32' ); + out = divide( mat, mat, { + 'dtype': 'int32' + }); + + assert.deepEqual( out.data, d2 ); + + // Multiply matrix by scalar and mutate... + out = divide( mat, 2, { + 'copy': false + }); + + assert.strictEqual( mat, out ); + assert.deepEqual( mat.data, d3 ); + }); + + it( 'should perform an element-wise division by a scalar and return a matrix of a specific type', function test() { + var mat, + out, + d1, + d2, + i; + + d1 = new Int16Array( 100 ); + d2 = new Uint16Array( 100 ); + for ( i = 0; i < d1.length; i++ ) { + d1[ i ] = i; + d2[ i ] = i / 2; } + mat = matrix( d1, [10,10], 'int16' ); + out = divide( mat, 2, { + 'dtype': 'uint16' + }); + + assert.strictEqual( out.dtype, 'uint16' ); + assert.deepEqual( out.data, d2 ); + }); + + it( 'should return an empty data structure if provided an empty data structure', function test() { + assert.deepEqual( divide( [], 1 ), [] ); + assert.deepEqual( divide( matrix( [0,0] ), 1 ).data, matrix( [0,0] ).data ); + assert.deepEqual( divide( new Int8Array(), 1 ), new Float64Array() ); }); }); diff --git a/test/test.matrix.js b/test/test.matrix.js new file mode 100644 index 0000000..4fd260e --- /dev/null +++ b/test/test.matrix.js @@ -0,0 +1,100 @@ +/* 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: + divide = require( './../lib/matrix.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'matrix divide', function tests() { + + var out1, out2, + mat, + d1, + d2, + d3, + i; + + d1 = new Float64Array( 25 ); + d2 = new Float64Array( 25 ); + d3 = new Float64Array( 25 ); + for ( i = 0; i < d1.length; i++ ) { + d1[ i ] = ( i + 1 ); + d2[ i ] = 1; + d3[ i ] = ( i + 1 ) / 3; + } + + beforeEach( function before() { + mat = matrix( d1, [5,5], 'float64' ); + out1 = matrix( d2, [5,5], 'float64' ); + out2 = matrix( d3, [5,5], 'float64' ); + }); + + it( 'should export a function', function test() { + expect( divide ).to.be.a( 'function' ); + }); + + it( 'should throw an error if provided unequal length matrices', function test() { + expect( badValues ).to.throw( Error ); + function badValues() { + divide( matrix( [10,10] ), mat, 1 ); + } + }); + + it( 'should throw an error if provided a matrix to be multiplied which is not of equal length to the input matrix', function test() { + expect( badValues ).to.throw( Error ); + function badValues() { + divide( matrix( [5,5] ), mat, matrix( [10,10] ) ); + } + }); + + it( 'should divide each matrix element by a scalar', function test() { + var actual; + + actual = matrix( [5,5], 'float64' ); + actual = divide( actual, mat, 3 ); + + assert.deepEqual( actual.data, out2.data ); + }); + + it( 'should divide two matrices by each other element-wise', function test() { + var actual; + + actual = matrix( [5,5], 'float64' ); + actual = divide( actual, mat, mat ); + + assert.deepEqual( actual.data, out1.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( divide( out, mat, 1 ).data, expected ); + + mat = matrix( [10,0] ); + assert.deepEqual( divide( out, mat, 1 ).data, expected ); + + mat = matrix( [0,0] ); + assert.deepEqual( divide( out, mat, 1 ).data, expected ); + }); + +}); diff --git a/test/test.typedarray.js b/test/test.typedarray.js new file mode 100644 index 0000000..431f12d --- /dev/null +++ b/test/test.typedarray.js @@ -0,0 +1,93 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + divide = require( './../lib/typedarray.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'typed-array divide', function tests() { + + it( 'should export a function', function test() { + expect( divide ).to.be.a( 'function' ); + }); + + it( 'should divide elements of two typed arrays', function test() { + var data, actual, expected, y; + + data = new Float64Array([ + 12, + 6, + 4, + 3 + ]); + y = new Float64Array([ + 4, + 3, + 4, + 1 + ]); + actual = new Float64Array( data.length ); + + actual = divide( actual, data, y ); + + expected = new Float64Array( [3,2,1,3] ); + + assert.deepEqual( expected, actual ); + }); + + it( 'should throw an error if provided two typed arrays of differing lengths', function test() { + expect( foo ).to.throw( Error ); + function foo() { + divide( new Array(2), new Int8Array( [1,2] ), new Int8Array( [1,2,3] ) ); + } + + expect( foo2 ).to.throw( Error ); + function foo2() { + divide( new Array(2), new Int8Array( [1,2] ), [ 1, 2, 3 ] ); + } + }); + + it( 'should handle non-numeric y values by setting the respective element to NaN', function test() { + var data, actual, expected, y; + + data = new Float64Array([ + 1, + 2, + 3, + 4 + ]); + actual = new Array( data.length ); + actual = divide( actual, data, null ); + + expected = [ NaN, NaN, NaN, NaN ]; + + assert.deepEqual( actual, expected ); + + actual = new Array( data.length ); + y = [ 1, 2, 3, null ]; + actual = divide( actual, data, y ); + + expected = [ 1, 1, 1, NaN ]; + + assert.deepEqual( actual, expected ); + + }); + + it( 'should return an empty array if provided an empty array', function test() { + assert.deepEqual( divide( new Int8Array(), new Int8Array() ), new Int8Array() ); + }); + +}); diff --git a/test/test.validate.js b/test/test.validate.js new file mode 100644 index 0000000..e21b68c --- /dev/null +++ b/test/test.validate.js @@ -0,0 +1,177 @@ +/* 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 path 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( {}, { + 'path': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return an error if provided a separator 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( {}, { + 'sep': 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, + 'deepset': true, + 'path': 'x/y', + 'sep': '/', + 'dtype': 'int32' + }); + + assert.isNull( err ); + + err = validate( {}, { + 'beep': true, // misc options + 'boop': 'bop' + }); + + assert.isNull( err ); + }); + +});