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..d8731b6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Athan Reines. +Copyright (c) 2014-2015 Athan Reines. 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..3234fbb 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/stylish.js + + # 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 cc41d5c..b563a4b 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,15 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse ## Usage -To use the module, - ``` javascript var lt = require( 'compute-lt' ); ``` -#### lt( arr, x ) +#### lt( arr, x[, opts] ) Computes an element-wise comparison (less than) for each input `array` element. `x` may either be an `array` of equal length or a single value (`number` or `string`). -The function returns an `array` with length equal to that of the input `array`. Each output `array` element is either `0` or `1`. A value of `1` means that an element is less than a compared value and `0` means that an element is __not__ less than a compared value. +The function returns an `array` with a length equal to that of the input `array`. Each output `array` element is either `0` or `1`. A value of `1` means that an element is less than a compared value and `0` means that an element is __not__ less than a compared value. ``` javascript var arr = [ 5, 3, 8, 3, 2 ], @@ -41,6 +39,82 @@ out = lt( arr, [ 6, 2, 6, 7, 3 ] ); // returns [ 1, 0, 0, 1, 1 ] ``` +The function accepts the following `options`: + +* __copy__: `boolean` indicating whether to return a new `array`. Default: `true`. +* __accessor__: accessor `function` for accessing values in object `arrays`. + +To mutate the input `array` (e.g., when input values can be discarded or when optimizing memory usage), set the `copy` option to `false`. + +``` javascript +var arr = [ 5, 3, 8, 3, 2 ]; + +var out = lt( arr, 4, { + 'copy': false +}); +// returns [ 0, 1, 0, 1, 1 ] + +console.log( arr === out ); +// returns true +``` + +For object `arrays`, provide an accessor `function` for accessing `array` values. + +``` javascript +var data = [ + ['beep', 5], + ['boop', 3], + ['bip', 8], + ['bap', 3], + ['baz', 2] +]; + +function getValue( d, i ) { + return d[ 1 ]; +} + +var out = lt( data, 4, { + 'accessor': getValue +}); +// returns [ 0, 1, 0, 1, 1 ] +``` + +When comparing values between two object `arrays`, provide an accessor `function` which accepts `3` arguments. + +``` javascript +var data = [ + ['beep', 5], + ['boop', 3], + ['bip', 8], + ['bap', 3], + ['baz', 2] +]; + +var arr = [ + {'x': 4}, + {'x': 5}, + {'x': 6}, + {'x': 5}, + {'x': 3} +]; + +function getValue( d, i, j ) { + if ( j === 0 ) { + return d[ 1 ]; + } + return d.x; +} + +var out = lt( data, arr, { + 'accessor': getValue +}); +// returns [ 0, 1, 0, 1, 1 ] +``` + +__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 (comparison) input `array`. + + + ## Examples @@ -50,7 +124,6 @@ var lt = require( 'compute-lt' ), // Simulate some data... var data = new Array( 100 ); - for ( var i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random()*100 ); } @@ -74,7 +147,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 @@ -98,15 +171,15 @@ $ make view-cov ``` +--- ## License [MIT license](http://opensource.org/licenses/MIT). ---- ## Copyright -Copyright © 2014. Athan Reines. +Copyright © 2014-2015. Athan Reines. [npm-image]: http://img.shields.io/npm/v/compute-lt.svg diff --git a/examples/index.js b/examples/index.js index 78b824e..5ec1822 100644 --- a/examples/index.js +++ b/examples/index.js @@ -5,7 +5,6 @@ var lt = require( './../lib' ), // Simulate some data... var data = new Array( 100 ); - for ( var i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random()*100 ); } diff --git a/lib/index.js b/lib/index.js index f3d5354..15b4712 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,76 +1,118 @@ -/** -* -* COMPUTE: lt -* -* -* DESCRIPTION: -* - Computes an element-wise comparison (less than) of an array. -* -* -* NOTES: -* [1] -* -* -* TODO: -* [1] -* -* -* LICENSE: -* MIT -* -* Copyright (c) 2014. Athan Reines. -* -* -* AUTHOR: -* Athan Reines. kgryte@gmail.com. 2014. -* -*/ - 'use strict'; +// MODULES // + +var isArray = require( 'validate.io-array' ), + isNumber = require( 'validate.io-number-primitive' ), + isString = require( 'validate.io-string-primitive' ), + isBoolean = require( 'validate.io-boolean-primitive' ), + isFunction = require( 'validate.io-function' ), + isObject = require( 'validate.io-object' ); + + // LESS THAN // /** -* FUNCTION: lt( arr, x ) +* FUNCTION: lt( arr, x[, opts] ) * Computes an element-wise comparison (less than) of an array. * -* @param {Array} arr - input array -* @param {Array|Number|String} x - comparator -* @returns {Array} array of 1s and 0s, where a `1` indicates that an input array element is less than a compared value and `0` indicates that an input array element is not less than a compared value +* @param {Number[]|Array} arr - input array +* @param {Number[]|Array|Number|String} x - comparator +* @param {Object} [opts] - function options +* @param {Boolean} [opts.copy=true] - boolean indicating whether to return a new array +* @param {Function} [opts.accessor] - accessor function for accessing array values +* @returns {Number[]} array of 1s and 0s, where a `1` indicates that an input array element is less than a compared value and `0` indicates that an input array element is not less than a compared value */ -function lt( arr, x ) { - var isArray = Array.isArray( x ), - type = typeof x, +function lt( arr, x, opts ) { + var isArr = isArray( x ), + copy = true, + arity, + clbk, out, len, i; - if ( !Array.isArray( arr ) ) { - throw new TypeError( 'lt()::invalid input argument. Must provide an array.' ); + if ( !isArray( arr ) ) { + throw new TypeError( 'lt()::invalid input argument. Must provide an array. Value: `' + arr + '`.' ); } - if ( !isArray && ( type !== 'number' || x !== x ) && type !== 'string' ) { - throw new TypeError( 'lt()::invalid input argument. Comparison input must either be an array, number, or string.' ); + if ( !isArr && !isNumber( x ) && !isString( x ) ) { + throw new TypeError( 'lt()::invalid input argument. Comparison input must either be an array, number primitive, or string primitive. Value: `' + x + '`.' ); + } + if ( arguments.length > 2 ) { + if ( !isObject( opts ) ) { + throw new TypeError( 'lt()::invalid input argument. Options argument must be an object. Value: `' + opts + '`.' ); + } + if ( opts.hasOwnProperty( 'copy' ) ) { + copy = opts.copy; + if ( !isBoolean( copy ) ) { + throw new TypeError( 'lt()::invalid option. Copy option must be a boolean primitive. Option: `' + copy + '`.' ); + } + } + if ( opts.hasOwnProperty( 'accessor' ) ) { + clbk = opts.accessor; + if ( !isFunction( clbk ) ) { + throw new TypeError( 'lt()::invalid option. Accessor must be a function. Option: `' + clbk + '`.' ); + } + arity = clbk.length; + } } len = arr.length; - out = new Array( len ); - if ( isArray ) { + if ( copy ) { + out = new Array( len ); + } else { + out = arr; + } + // Case 1: comparison array + if ( isArr ) { if ( len !== x.length ) { throw new Error( 'lt()::invalid input argument. Comparison array must have a length equal to that of the input array.' ); } + if ( arity === 3 ) { // clbk implied + for ( i = 0; i < len; i++ ) { + if ( clbk( arr[i], i, 0 ) < clbk( x[i], i, 1 ) ) { + out[ i ] = 1; + } else { + out[ i ] = 0; + } + } + } + else if ( clbk ) { + for ( i = 0; i < len; i++ ) { + if ( clbk( arr[i], i ) < x[ i ] ) { + out[ i ] = 1; + } else { + out[ i ] = 0; + } + } + } + else { + for ( i = 0; i < len; i++ ) { + if ( arr[ i ] < x[ i ] ) { + out[ i ] = 1; + } else { + out[ i ] = 0; + } + } + } + } + // Case 2: accessor and single comparator + else if ( clbk ) { for ( i = 0; i < len; i++ ) { - if ( arr[ i ] < x[ i ] ) { + if ( clbk( arr[ i ], i ) < x ) { out[ i ] = 1; } else { out[ i ] = 0; } } - return out; } - for ( i = 0; i < len; i++ ) { - if ( arr[ i ] < x ) { - out[ i ] = 1; - } else { - out[ i ] = 0; + // Case 3: single comparator + else { + for ( i = 0; i < len; i++ ) { + if ( arr[ i ] < x ) { + out[ i ] = 1; + } else { + out[ i ] = 0; + } } } return out; diff --git a/package.json b/package.json index 09a9b5b..33dafb4 100644 --- a/package.json +++ b/package.json @@ -33,18 +33,28 @@ "mathematics", "math", "compare", - "less than" + "less than", + "lt" ], "bugs": { "url": "https://github.com/compute-io/lt/issues" }, - "dependencies": {}, + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-boolean-primitive": "^1.0.0", + "validate.io-function": "^1.0.2", + "validate.io-number-primitive": "^1.0.0", + "validate.io-object": "^1.0.3", + "validate.io-string-primitive": "^1.0.0" + }, "devDependencies": { - "chai": "1.x.x", - "compute-sum": "^1.0.0", + "chai": "2.x.x", + "compute-sum": "^1.1.0", "coveralls": "^2.11.1", "istanbul": "^0.3.0", - "mocha": "1.x.x" + "jshint": "2.x.x", + "jshint-stylish": "^1.0.0", + "mocha": "2.x.x" }, "licenses": [ { diff --git a/test/test.js b/test/test.js index 3c45bd2..089ca1b 100644 --- a/test/test.js +++ b/test/test.js @@ -1,3 +1,4 @@ +/* global require, describe, it */ 'use strict'; // MODULES // @@ -46,8 +47,10 @@ describe( 'compute-lt', function tests() { } }); - it( 'should throw an error if a comparison value which is not an array of equal length, number, or string', function test() { + it( 'should throw an error if provided a comparison value which is not an array, number primitive, or string primitive', function test() { var values = [ + new Number( 5 ), + new String( '5' ), null, undefined, NaN, @@ -62,7 +65,80 @@ describe( 'compute-lt', function tests() { function badValue( value ) { return function() { - lt( [], value ); + lt( [1,2,3], value ); + }; + } + }); + + it( 'should throw an error if provided an options argument which is not an object', function test() { + var values = [ + '5', + 5, + null, + undefined, + NaN, + true, + [], + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( TypeError ); + } + + function badValue( value ) { + return function() { + lt( [1,2,3], 10, value ); + }; + } + }); + + it( 'should throw an error if provided a copy option which is not a boolean primitive', function test() { + var values = [ + '5', + 5, + null, + undefined, + NaN, + new Boolean( true ), + {}, + [], + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( TypeError ); + } + + function badValue( value ) { + return function() { + lt( [1,2,3], 10, { + 'copy': value + }); + }; + } + }); + + it( 'should throw an error if provided an accessor option which is not a function', function test() { + var values = [ + '5', + 5, + null, + undefined, + NaN, + {}, + [] + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( TypeError ); + } + + function badValue( value ) { + return function() { + lt( [1,2,3], 10, { + 'accessor': value + }); }; } }); @@ -74,12 +150,21 @@ describe( 'compute-lt', function tests() { } }); + it( 'should not mutate an input array by default', function test() { + var data, actual; + + data = [ 4, 5, 3, 6, 8 ]; + actual = lt( data, 4 ); + + assert.ok( data !== actual ); + }); + it( 'should correctly compare values', function test() { var data, expected, actual; data = [ 4, 5, 3, 6, 8 ]; - // Single numeric value: + // Single numeric comparator: actual = lt( data, 4 ); expected = [ 0, 0, 1, 0, 0 ]; @@ -110,4 +195,101 @@ describe( 'compute-lt', function tests() { assert.deepEqual( actual, expected ); }); + it( 'should mutate an input array if the `copy` option is `false`', function test() { + var data, expected, actual; + + data = [ 4, 5, 3, 6, 8 ]; + + actual = lt( data, 4, { + 'copy': false + }); + expected = [ 0, 0, 1, 0, 0 ]; + + assert.ok( data === actual ); + assert.deepEqual( actual, expected ); + }); + + it( 'should correctly compare values using an accessor', function test() { + var data, expected, actual; + + data = [ + [1,4], + [2,5], + [3,3], + [4,6], + [5,8] + ]; + + // Single numeric comparator: + actual = lt( data, 4, { + 'accessor': function getValue( d ) { + return d[ 1 ]; + } + }); + expected = [ 0, 0, 1, 0, 0 ]; + + assert.deepEqual( actual, expected ); + + // Single string comparator: + data = [ + {'x': 'a'}, + {'x': 'aa'}, + {'x': 'aaa'}, + {'x': 'b'} + ]; + + actual = lt( data, 'aa', { + 'accessor': function getValue( d ) { + return d.x; + } + }); + expected = [ 1, 0, 0, 0 ]; + + assert.deepEqual( actual, expected ); + }); + + it( 'should correctly compare values when provided a comparison array and using an accessor', function test() { + var data, arr, expected, actual; + + data = [ + [1,4], + [2,5], + [3,3], + [4,6], + [5,8] + ]; + + arr = [ 5, 5, 5, 5, 9 ]; + + actual = lt( data, arr, { + 'accessor': function getValue( d ) { + return d[ 1 ]; + } + }); + expected = [ 1, 0, 1, 0, 1 ]; + + assert.deepEqual( actual, expected ); + + // Both arrays are accessed: + arr = [ + {'x':5}, + {'x':5}, + {'x':5}, + {'x':5}, + {'x':9} + ]; + + actual = lt( data, arr, { + 'accessor': function getValue( d, i, j ) { + if ( j === 0 ) { + return d[ 1 ]; + } + return d.x; + } + }); + expected = [ 1, 0, 1, 0, 1 ]; + + assert.deepEqual( actual, expected ); + }); + });