From f144c1837b4806380371c4901ebe350458dd389d Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Mon, 17 Aug 2015 10:19:42 +0200 Subject: [PATCH 1/6] [UPDATE] dotfiles, LICENSE, Makefile --- .gitignore | 87 ++++++++++++++++++++++++++++++++++++++++++++++++--- .jshintignore | 14 +++++++++ .npmignore | 2 ++ .travis.yml | 11 +++++-- LICENSE | 4 +-- Makefile | 37 +++++++++++++++++----- 6 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 .jshintignore diff --git a/.gitignore b/.gitignore index cc6c4b6..1c97e21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ +# Files # +######### + + # Directories # ############### reports/ @@ -12,6 +16,28 @@ build/ *.exe *.o *.so +*.slo +*.lo +*.obj +*.dylib +*.dll +*.lai +*.la +*.a +*.lib +*.ko +*.elf + +# Precompiled headers # +####################### +*.gch +*.pch + +# Executables # +############### +*.exe +*.out +*.app # Packages # ############ @@ -51,12 +77,65 @@ Desktop.ini # Node.js # ########### /node_modules/ +pids +*.pid +*.seed # Matlab # ########## - -# Windows default autosave extension *.asv - -# Compiled MEX binaries (all platforms) *.mex* + +# Fortran # +########### +*.mod + +# R # +##### +.Rhistory +.Rapp.history +.Rproj.user/ + +# TeX # +####### +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.dvi +*-converted-to.* +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.brf +*.run.xml +*.fdb_latexmk +*.synctex +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync +*.alg +*.loa +acs-*.bib +*.thm +*.nav +*.snm +*.vrb +*.acn +*.acr +*.glg +*.glo +*.gls +*.brf +*-concordance.tex +*.tikz +*-tikzDictionary +*.idx +*.ilg +*.ind +*.ist diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..3163c22 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,14 @@ + +# Directories # +############### +build/ +reports/ +dist/ + +# Node.js # +########### +/node_modules/ + +# Git # +####### +.git* diff --git a/.npmignore b/.npmignore index cc43218..9db298d 100644 --- a/.npmignore +++ b/.npmignore @@ -13,6 +13,7 @@ examples/ reports/ support/ test/ +benchmark/ # Node.js # ########### @@ -46,5 +47,6 @@ Desktop.ini # Utilities # ############# .jshintrc +.jshintignore .travis.yml .editorconfig diff --git a/.travis.yml b/.travis.yml index 7c16620..eeabf4b 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 codecov + 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..e1d36c2 100644 --- a/Makefile +++ b/Makefile @@ -5,25 +5,30 @@ # 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 +36,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,13 +107,24 @@ 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 # -# Installing node_modules: +# Install node_modules: .PHONY: install install: @@ -117,7 +139,6 @@ clean-node: # CLEAN # - .PHONY: clean clean: From 3ccf8a672a9cd083742297f5daa496205318c4c1 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Mon, 17 Aug 2015 10:46:29 +0200 Subject: [PATCH 2/6] [UPDATE] API changes, support for typed arrays, copy, accessor + dtype options --- lib/index.js | 150 +++++++++++++++++++++++++++++++++--------------- lib/validate.js | 59 +++++++++++++++++++ 2 files changed, 162 insertions(+), 47 deletions(-) create mode 100644 lib/validate.js diff --git a/lib/index.js b/lib/index.js index ae0cb03..ecf6ee7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,33 +1,14 @@ -/** -* -* COMPUTE: unique -* -* -* DESCRIPTION: -* - Removes duplicate values from a numeric array. -* -* -* NOTES: -* [1] -* -* -* TODO: -* [1] -* -* -* LICENSE: -* MIT -* -* Copyright (c) 2014. Athan Reines. -* -* -* AUTHOR: -* Athan Reines. kgryte@gmail.com. 2014. -* -*/ - 'use strict'; +// MODULES // + +var cast = require( 'compute-cast-arrays' ), + ctors = require( 'compute-array-constructors' ), + isArrayLike = require( 'validate.io-array-like' ), + isTypedArrayLike = require( 'validate.io-typed-array-like' ), + validate = require( './validate.js' ); + + // FUNCTIONS // /** @@ -47,39 +28,114 @@ function ascending( a, b ) { // UNIQUE // /** -* FUNCTION: unique( arr, sorted ) -* Removes duplicate values from a numeric array. Note: the input array is mutated. +* FUNCTION: unique( arr[, opts] ) +* Removes duplicate values from an array. * * @param {Array} arr - array to be deduped -* @param {Boolean} sorted - boolean flag indicating if the input array is sorted +* @param {Object} [opts] - function options +* @param {Boolean} [opts.copy=true] - boolean indicating if the function should return a new data structure +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {Boolean} [opts.sorted=false] - boolean indicating if the input array is already sorted +* @param {String} [opts.dtype="float64"] - output data type +* @returns {Array} output array */ -function unique( arr, sorted ) { - if ( !Array.isArray( arr ) ) { - throw new TypeError( 'unique()::invalid input argument. First argument must be an array.' ); - } - if ( arguments.length > 1 && typeof sorted !== 'boolean' ) { - throw new TypeError( 'unique()::invalid input argument. Second argument must be an array.' ); - } - var len = arr.length, +function unique( arr, options ) { + /* jshint newcap:false */ + var opts = {}, + ctor, + dt, + err, + len, + out, i, j, val; + if ( arguments.length > 1 ) { + err = validate( opts, options ); + if ( err ) { + throw err; + } + } + + if ( isTypedArrayLike( arr ) ) { + len = arr.length; + if ( opts.copy !== false ) { + out = arr; + } else { + if ( opts.dtype ) { + out = cast( arr, opts.dtype ); + } else { + out = Array.prototype.slice.call( arr ); + } + } + } else if ( isArrayLike( arr ) ) { + len = arr.length; + if ( opts.copy !== false ) { + if ( opts.accessor ) { + if ( opts.dtype ) { + ctor = ctors( opts.dtype ); + if ( ctor === null ) { + throw new Error( 'unique()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + dt + '`.' ); + } + out = new ctor( len ); + } else { + out = new Array( len ); + } + for ( i = 0; i < len; i++ ) { + out[ i ] = opts.accessor( arr[ i ] ); + } + } else { + if ( opts.dtype ) { + out = cast( arr, opts.dtype ); + } else { + out = arr.slice(); + } + } + } else { + if ( opts.accessor ) { + for ( i = 0; i < len; i++ ) { + arr[ i ] = opts.accessor( arr[ i ] ); + } + } + out = arr; + } + } else { + throw new TypeError( 'unique()::invalid input argument. First argument must be an array or typed array.' ); + } + if ( !len ) { - return; + return out; } - if ( !sorted ) { - arr.sort( ascending ); + + if ( opts.sorted !== true ) { + Array.prototype.sort.call( out, ascending ); } - // Loop through the array, only incrementing a pointer when successive values are different. When a succeeding value is different, move the pointer and set the next value. In the trivial case where all array elements are unique, we incur a slight penalty in resetting the element value for each unique value. In other cases, we simply move a unique value to a new position in the array. The end result is a sorted array with unique values. + + /* + Loop through the array, only incrementing a pointer when successive values are different. + When a succeeding value is different, move the pointer and set the next value. + In the trivial case where all array elements are unique, + we incur a slight penalty in resetting the element value for each unique value. + In other cases, we simply move a unique value to a new position in the array. + The end result is a sorted array with unique values. + */ + for ( i = 1, j = 0; i < len; i++ ) { - val = arr[ i ]; - if ( arr[ j ] !== val ) { + val = out[ i ]; + if ( out[ j ] !== val ) { j++; - arr[ j ] = val; + out[ j ] = val; } } + // Truncate the array: - arr.length = j+1; + if ( isTypedArrayLike( out ) ) { + out = out.subarray( 0, j + 1 ); + } else { + out.length = j + 1; + } + return out; + } // end FUNCTION unique() diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..3128a51 --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,59 @@ +'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] - boolean indicating if the function should return a new data structure +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {Boolean} [options.sorted] - boolean indicating if the input array is already sorted +* @param {String} [options.dtype] - output data type +* @returns {Null|Error} null or an error +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( 'unique()::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( 'unique()::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( 'unique()::invalid option. Accessor must be a function. Option: `' + opts.accessor + '`.' ); + } + } + if ( options.hasOwnProperty( 'sorted' ) ) { + opts.sorted = options.sorted; + if ( !isBoolean( opts.sorted ) ) { + return new TypeError( 'unique()::invalid option. Sorted option must be a boolean primitive. Option: `' + opts.copy + '`.' ); + } + } + if ( options.hasOwnProperty( 'dtype' ) ) { + opts.dtype = options.dtype; + if ( !isString( opts.dtype ) ) { + return new TypeError( 'beta()::invalid option. Data type option must be a string primitive. Option: `' + opts.dtype + '`.' ); + } + } + return null; +} // end FUNCTION validate() + + +// EXPORTS // + +module.exports = validate; From 9d5f00fb4bd69e6483acd40b6c54115ae937340a Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Mon, 17 Aug 2015 11:04:45 +0200 Subject: [PATCH 3/6] [UPDATE] finish tests --- lib/index.js | 20 ++--- test/test.js | 187 +++++++++++++++++++++++++++++++++++++++--- test/test.validate.js | 154 ++++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 23 deletions(-) create mode 100644 test/test.validate.js diff --git a/lib/index.js b/lib/index.js index ecf6ee7..22dec82 100644 --- a/lib/index.js +++ b/lib/index.js @@ -59,7 +59,7 @@ function unique( arr, options ) { if ( isTypedArrayLike( arr ) ) { len = arr.length; - if ( opts.copy !== false ) { + if ( opts.copy === false ) { out = arr; } else { if ( opts.dtype ) { @@ -70,12 +70,19 @@ function unique( arr, options ) { } } else if ( isArrayLike( arr ) ) { len = arr.length; - if ( opts.copy !== false ) { + if ( opts.copy === false ) { + if ( opts.accessor ) { + for ( i = 0; i < len; i++ ) { + arr[ i ] = opts.accessor( arr[ i ] ); + } + } + out = arr; + } else { if ( opts.accessor ) { if ( opts.dtype ) { ctor = ctors( opts.dtype ); if ( ctor === null ) { - throw new Error( 'unique()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + dt + '`.' ); + throw new Error( 'unique()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + opts.dtype + '`.' ); } out = new ctor( len ); } else { @@ -91,13 +98,6 @@ function unique( arr, options ) { out = arr.slice(); } } - } else { - if ( opts.accessor ) { - for ( i = 0; i < len; i++ ) { - arr[ i ] = opts.accessor( arr[ i ] ); - } - } - out = arr; } } else { throw new TypeError( 'unique()::invalid input argument. First argument must be an array or typed array.' ); diff --git a/test/test.js b/test/test.js index 9339b83..54ea1e1 100644 --- a/test/test.js +++ b/test/test.js @@ -23,9 +23,8 @@ describe( 'compute-unique', function tests() { expect( unique ).to.be.a( 'function' ); }); - it( 'should throw an error if not provided an array', function test() { + it( 'should throw an error if not provided an array-like object', function test() { var values = [ - '5', 5, null, NaN, @@ -41,56 +40,218 @@ describe( 'compute-unique', function tests() { function badValue( value ) { return function() { - unique( value, true ); + unique( value ); }; } }); - it( 'should throw an error if second argument is not a boolean', function test() { + it( 'should throw an error if provided an invalid option', function test() { var values = [ '5', 5, + true, + undefined, null, NaN, - undefined, [], - {}, - function(){} + {} ]; for ( var i = 0; i < values.length; i++ ) { expect( badValue( values[i] ) ).to.throw( TypeError ); } + function badValue( value ) { + return function() { + unique( [1,2,3], { + 'accessor': value + }); + }; + } + }); + it( 'should throw an error if provided an array and an unrecognized/unsupported data type option', function test() { + var values = [ + 'beep', + 'boop' + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( Error ); + } function badValue( value ) { return function() { - unique( [], value ); + unique( [1,2,3], { + 'dtype': value + }); }; } }); + it( 'should throw an error if provided an array and an unrecognized/unsupported data type option when using an accessor', function test() { + var values = [ + 'beep', + 'boop' + ]; + + for ( var i = 0; i < values.length; i++ ) { + expect( badValue( values[i] ) ).to.throw( Error ); + } + function badValue( value ) { + return function() { + unique( [{'x':1},{'x':2},{'x':3}], { + 'accessor': getValue, + 'dtype': value + }); + }; + } + + function getValue( d ) { + return d.x; + } + }); + it( 'should find the unique values of a sorted array', function test() { - var data, expected; + var data, expected, out; data = [ 1, 1, 2, 2, 3, 3, 7, 7 ]; expected = [ 1, 2, 3, 7 ]; - unique( data, true ); + out = unique( data, { + 'sorted': true + }); - assert.deepEqual( data, expected ); + assert.deepEqual( out, expected ); + }); + + it( 'should find the unique values of a sorted array and cast result to a different dtype', function test() { + var data, expected, out, i; + + data = [ 1, 1, 2, 2, 3, 3, 7, 7 ]; + + expected = new Int32Array( [ 1, 2, 3, 7 ] ); + + out = unique( data, { + 'sorted': true, + 'dtype': 'int32' + }); + + for ( i = 0; i < out.length; i++ ) { + assert.strictEqual( out[ i ], expected[ i ] ); + } + }); + + it( 'should find the unique values of a sorted typed array and cast result to a different dtype', function test() { + var data, expected, out, i; + + data = new Int16Array( [ 1, 1, 2, 2, 3, 3, 7, 7 ] ); + + expected = new Int32Array( [ 1, 2, 3, 7 ] ); + + out = unique( data, { + 'sorted': true, + 'dtype': 'int32' + }); + + for ( i = 0; i < out.length; i++ ) { + assert.strictEqual( out[ i ], expected[ i ] ); + } + }); + + it( 'should find the unique values of a sorted array using an accessor and cast result to a different dtype', function test() { + var data, expected, out, i; + + data = [ {'x':1}, {'x':1}, {'x':2}, {'x':2}, {'x':3}, {'x':3}, {'x':7}, {'x':7} ]; + + expected = new Int32Array( [ 1, 2, 3, 7 ] ); + + out = unique( data, { + 'sorted': true, + 'accessor': getValue, + 'dtype': 'int32' + }); + + for ( i = 0; i < out.length; i++ ) { + assert.strictEqual( out[ i ], expected[ i ] ); + } + + function getValue( d ) { + return d.x; + } }); + it( 'should find the unique values of an unsorted array and return sorted results', function test() { - var data, expected; + var data, expected, out; data = [ 2, 1, 3, 1, 5, 7, 1, 13, 7, 9 ]; expected = [ 1, 2, 3, 5, 7, 9, 13 ]; - unique( data ); + out = unique( data ); + + assert.deepEqual( out, expected ); + + // Mutate the input array: + out = unique( data, { + 'copy': false + }); assert.deepEqual( data, expected ); + + }); + + it( 'should find the unique values of an unsorted typed array and return the sorted results', function test() { + var data, expected, out, i; + + data = new Int32Array( [ 2, 1, 3, 1, 5, 7, 1, 13, 7, 9 ] ); + + expected = new Int32Array( [ 1, 2, 3, 5, 7, 9, 13 ] ); + + out = unique( data ); + + for ( i = 0; i < out.length; i++ ) { + assert.strictEqual( out[ i ], expected[ i ] ); + } + + // Mutate the input array: + out = unique( data, { + 'copy': false + }); + + for ( i = 0; i < out.length; i++ ) { + assert.strictEqual( data[ i ], expected[ i ] ); + } + + }); + + it( 'should find the unique values of an unsorted object array using an accessor function and return the sorted results', function test() { + var data, expected, out; + + data = [ + {'x':2}, {'x':1}, {'x':3}, {'x':1}, {'x':5}, {'x':7}, {'x':1}, {'x':13}, {'x':7}, {'x':9} + ]; + + expected = [ 1, 2, 3, 5, 7, 9, 13 ]; + + out = unique( data, { + 'accessor': getValue + }); + + assert.deepEqual( out, expected ); + + // Mutate the input array: + out = unique( data, { + 'accessor': getValue, + 'copy': false + }); + + assert.deepEqual( data, expected ); + + function getValue( d ) { + return d.x; + } + }); it( 'should do nothing if provided an empty array', function test() { diff --git a/test/test.validate.js b/test/test.validate.js new file mode 100644 index 0000000..76f43ba --- /dev/null +++ b/test/test.validate.js @@ -0,0 +1,154 @@ +/* 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 sorted 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( {}, { + 'sorted': 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, + 'sorted': false, + 'dtype': 'int32' + }); + + assert.isNull( err ); + + err = validate( {}, { + 'beep': true, // misc options + 'boop': 'bop' + }); + + assert.isNull( err ); + }); + +}); From 328f60cf9407c709e90677e24586d05a44c94fd0 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Mon, 17 Aug 2015 11:20:21 +0200 Subject: [PATCH 4/6] [UPDATE] README.md, dependencies --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++---------- package.json | 44 ++++++++++++++++--------- 2 files changed, 104 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index bf81ebf..39f5648 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ unique === -[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Dependencies][dependencies-image]][dependencies-url] +[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][codecov-image]][codecov-url] [![Dependencies][dependencies-image]][dependencies-url] -> Removes duplicate values from a numeric array. +> Removes duplicate values from an array. ## Installation @@ -24,27 +24,86 @@ var unique = require( 'compute-unique' ); #### unique( arr[, sorted] ) -Removes duplicate values from a numeric `array`. If the `array` is already sorted in __ascending__ order, set the optional second argument to `true`. +Removes duplicate values from [`array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) or [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) `arr`. ``` javascript -var unsorted = [ 3, 1, 1, 4, 3, 5 ], - sorted = [ 1, 1, 3, 3, 4, 5 ]; +var data = [ 3, 1, 1, 4, 3, 5 ]; -unique( unsorted ); +unique( data ); // [ 1, 3, 4, 5 ] +``` + +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`. +* __sorted__: `boolean` indicating if the `array` is already sorted in __ascending__ order. Default: `false`. + + +If the `array` is already sorted in __ascending__ order, set the `sorted` option to `true`. -unique( sorted, true ); +```javascript +var sorted = [ 1, 1, 3, 3, 4, 5 ]; + +unique( sorted, { + 'sorted': true +}); // [ 1, 3, 4, 5 ] ``` -Note: the input `array` is mutated. To avoid unwanted mutation of the original `array`, +For non-numeric `arrays`, provide an accessor `function` for accessing `array` values. ``` javascript -var copy = unsorted.slice(); +var data = [ + ['beep', 3], + ['boop', 1], + ['bip', 1], + ['bap', 4], + ['baz', 3] + ['boz', 5] +]; + +function getValue( d, i ) { + return d[ 1 ]; +} -unique( copy ); +var out = unique( data, { + 'accessor': getValue +}); +// [ 1, 3, 4, 5 ] ``` +By default, when provided a [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays), the output date type is the same as the one of the input array. 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 data, out; + +data = new Int32Array( [3, 1, 1, 4, 3, 5] ); + +out = unique( data, { + 'dtype': 'int32' +}); +// returns Int32Array( [1,3,4,5] ) +``` + +By default, the function returns a new data structure. To mutate the input data structure (e.g., when input values can be discarded or when optimizing memory usage), set the `copy` option to `false`. + +``` javascript +var data, + out, + bool; + +data = [ 3, 1, 1, 4, 3, 5 ]; + +out = unique( data, { + 'copy': false +}); +// returns [ 1, 3, 4, 5] + +bool = ( data === out ); +// returns true +``` ## Examples @@ -55,7 +114,7 @@ var unique = require( 'compute-unique' ); var data = new Array( 1000 ); for ( var i = 0; i < data.length; i++ ) { - data[ i ] = Math.round( Math.random()*10 ); + data[ i ] = Math.round( Math.random() * 10 ); } // Determine the unique values: @@ -84,7 +143,7 @@ A couple of notes: ### 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 @@ -108,15 +167,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-unique.svg @@ -125,8 +184,8 @@ Copyright © 2014. Athan Reines. [travis-image]: http://img.shields.io/travis/compute-io/unique/master.svg [travis-url]: https://travis-ci.org/compute-io/unique -[coveralls-image]: https://img.shields.io/coveralls/compute-io/unique/master.svg -[coveralls-url]: https://coveralls.io/r/compute-io/unique?branch=master +[codecov-image]: https://img.shields.io/codecov/c/github/compute-io/unique/master.svg +[codecov-url]: https://codecov.io/github/compute-io/unique?branch=master [dependencies-image]: http://img.shields.io/david/compute-io/unique.svg [dependencies-url]: https://david-dm.org/compute-io/unique diff --git a/package.json b/package.json index a9feead..98f2c6b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "compute-unique", "version": "1.0.1", - "description": "Removes duplicate values from a numeric array.", + "description": "Removes duplicate values from an array.", "author": { "name": "Athan Reines", "email": "kgryte@gmail.com" @@ -10,12 +10,16 @@ { "name": "Athan Reines", "email": "kgryte@gmail.com" + }, + { + "name": "Philipp Burckhardt", + "email": "pburckhardt@outlook.com" } ], "scripts": { - "test": "./node_modules/.bin/mocha", - "test-cov": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --dir ./reports/coverage -- -R spec", - "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --dir ./reports/coveralls/coverage --report lcovonly -- -R spec && cat ./reports/coveralls/coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./reports/coveralls" + "test": "mocha", + "test-cov": "istanbul cover ./node_modules/.bin/_mocha --dir ./reports/coverage -- -R spec", + "codecov": "istanbul cover ./node_modules/.bin/_mocha --dir ./reports/codecov/coverage --report lcovonly -- -R spec && cat ./reports/codecov/coverage/lcov.info | codecov && rm -rf ./reports/codecov" }, "main": "./lib", "repository": { @@ -30,22 +34,30 @@ "math", "set", "unique", - "array" + "array", + "typed-array" ], "bugs": { "url": "https://github.com/compute-io/unique/issues" }, - "dependencies": {}, + "dependencies": { + "compute-array-constructors": "^1.0.0", + "compute-cast-arrays": "^1.0.0", + "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-object": "^1.0.4", + "validate.io-string-primitive": "^1.0.0", + "validate.io-typed-array-like": "^1.0.0" + }, "devDependencies": { - "chai": "1.x.x", - "mocha": "1.x.x", - "coveralls": "^2.11.1", - "istanbul": "^0.3.0" + "chai": "3.x.x", + "codecov.io": "^0.1.5", + "istanbul": "^0.3.0", + "jshint": "2.x.x", + "jshint-stylish": "2.x.x", + "mocha": "2.x.x" }, - "licenses": [ - { - "type": "MIT", - "url": "http://www.opensource.org/licenses/MIT" - } - ] + "license": "MIT" } From 0ef2ac3264f5c3150d84f7412505dd43a566ce45 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Tue, 18 Aug 2015 10:54:44 +0200 Subject: [PATCH 5/6] [UPDATE] add TODO item --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 27aeb4e..c1407ea 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ TODO ==== - +1. Decide whether to `slice` or a for-loop to copy input array. For-loops are faster for smaller arrays, but `slice` seems to be more performant as the array length increases. Proper benchmarking needed. From 417377f37ce562a42da1c3f9b9beccd206c1184b Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Wed, 19 Aug 2015 20:50:49 +0200 Subject: [PATCH 6/6] [FIX] correctly copy typed array --- lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 22dec82..122fa5a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -65,7 +65,8 @@ function unique( arr, options ) { if ( opts.dtype ) { out = cast( arr, opts.dtype ); } else { - out = Array.prototype.slice.call( arr ); + // Copy typed array: + out = new arr.constructor( arr ); } } } else if ( isArrayLike( arr ) ) {