diff --git a/README.md b/README.md index 192721d..7c1d468 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,17 @@ kmeans.clusterize(vectors, {k: 4}, (err,res) => { if (err) console.error(err); else console.log('%o',res); }); + +// Example with promise +await const res = kmeans.clusterize(vectors, {k: 4}); +console.log('%o',res); ``` ## Inputs - **vectors** is a nXm array (n [lines] : number of points, m [columns] : number of dimensions) - **options** object: - **k** : number of clusters - **distance** (optional) : custom distance function returning the distance between two points `(a,b) => number`, *default* Euclidian Distance - - **callback** node-style callback taking error and result argument + - **callback** (optional) node-style callback taking error and result argument. If not supplied, the function returns a promise with the result. ## Outputs An array of objects (one for each cluster) with the following properties: diff --git a/lib/kmeans.js b/lib/kmeans.js index bebd45f..8f72fa5 100644 --- a/lib/kmeans.js +++ b/lib/kmeans.js @@ -231,6 +231,21 @@ class Clusterize { } exports.clusterize = (vector, options, callback) => { + if (!options || !vector) { + throw new Error('Provide 2 arguments: vector, options'); + } + + if (vector && options && !callback) { + return new Promise((resolve, reject) => { + /* eslint-disable no-new */ + new Clusterize(vector, options, (err, data) => { + if (err !== null) { + return reject(err); + } + return resolve(data); + }); + }); + } return new Clusterize(vector, options, callback); }; diff --git a/package-lock.json b/package-lock.json index 6ce21ba..d4b9a4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,11 +99,6 @@ "concat-map": "0.0.1" } }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=" - }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -171,6 +166,11 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -238,6 +238,11 @@ "rimraf": "2.6.1" } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -596,6 +601,11 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, "has": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", @@ -614,6 +624,11 @@ "ansi-regex": "2.1.1" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -825,26 +840,27 @@ } }, "mocha": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", - "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", + "browser-stdout": "1.3.1", + "commander": "2.15.1", "debug": "3.1.0", - "diff": "3.3.1", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.3", + "growl": "1.10.5", "he": "1.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, "debug": { "version": "3.1.0", @@ -854,27 +870,12 @@ "ms": "2.0.0" } }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" - }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -1123,25 +1124,23 @@ "dev": true }, "should": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz", - "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==", + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "requires": { "should-equal": "2.0.0", "should-format": "3.0.3", "should-type": "1.4.0", "should-type-adaptors": "1.0.1", "should-util": "1.0.0" - }, - "dependencies": { - "should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "requires": { - "should-type": "1.4.0" - } - } + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "requires": { + "should-type": "1.4.0" } }, "should-format": { diff --git a/test/index.js b/test/index.js index 98e5d02..446ca01 100644 --- a/test/index.js +++ b/test/index.js @@ -30,20 +30,30 @@ const data3D = [ * Tests */ describe('kmeans', () => { + describe('#clusterize()', () => { + it('should return a promise if no callback function', () => { + /* eslint-disable new-cap */ + kmeans.clusterize([], {}).should.be.Promise(); + }); + }); + describe('#clusterize() errors', () => { - it('should throw an error if there aren\'t 3 arguments', () => { + it('should throw an error if there aren\'t 2 arguments', () => { (() => { kmeans.clusterize(); - }).should.throw('Provide 3 arguments: vector, options, callback'); + }).should.throw('Provide 2 arguments: vector, options'); (() => { kmeans.clusterize({}); - }).should.throw('Provide 3 arguments: vector, options, callback'); + }).should.throw('Provide 2 arguments: vector, options'); + }); + + it('should not throw an error if there are 2 arguments', () => { (() => { kmeans.clusterize({}, {}); - }).should.throw('Provide 3 arguments: vector, options, callback'); + }).should.not.throw(); }); - it('should throw an error if no callback function', () => { + it('should throw an error if callback is no function', () => { (() => { kmeans.clusterize([], {}, {}); }).should.throw('Provide a callback function'); @@ -57,6 +67,10 @@ describe('kmeans', () => { }); }); + it('should reject promise if no \'k\' option', () => { + kmeans.clusterize({}, { k: 3 }).should.be.rejected(); + }); + it('should return an error if the data vector is not an array', done => { kmeans.clusterize({}, { k: 3 }, (err, res) => { should.not.exist(res); @@ -85,6 +99,10 @@ describe('kmeans', () => { }); }); + it('should return a result (array) with promise', () => { + kmeans.clusterize(data3D, { k: 3 }).should.be.eventually.have.length(3); + }); + it('should return 2 groups with the 2 vectors', done => { kmeans.clusterize([[1, 1], [2, 2]], { k: 2 }, (err, res) => { should.not.exist(err);