diff --git a/lib/expression/docs/function/probability/distribution.js b/lib/expression/docs/function/probability/distribution.js index 97494f3a3..a60a021f6 100644 --- a/lib/expression/docs/function/probability/distribution.js +++ b/lib/expression/docs/function/probability/distribution.js @@ -8,7 +8,7 @@ module.exports = { 'description': 'Create a distribution object of a specific type. ' + 'A distribution object contains functions `random([size,] [min,] [max])`, ' + - '`randomInt([size,] [min,] [max])`, and `pickRandom(array)`. ' + + '`randomInt([size,] [min,] [max])`, `pickRandom(array)` and `pickMultipleRandom(array)`. ' + 'Available types of distributions: "uniform", "normal". ' + 'Note that the function distribution is currently not available via the expression parser.', 'examples': [ diff --git a/lib/expression/docs/function/probability/pickMultipleRandom.js b/lib/expression/docs/function/probability/pickMultipleRandom.js new file mode 100644 index 000000000..79b39e8c1 --- /dev/null +++ b/lib/expression/docs/function/probability/pickMultipleRandom.js @@ -0,0 +1,13 @@ +module.exports = { + 'name': 'pickMultipleRandom', + 'category': 'Probability', + 'syntax': [ + 'pickMultipleRandom(array, number)' + ], + 'description': + 'Pick a given number of random entries from a given array.', + 'examples': [ + 'pickRandom([1, 3, 1, 6], 2)' + ], + 'seealso': ['random', 'randomInt', 'pickRandom'] +}; diff --git a/lib/expression/docs/function/probability/random.js b/lib/expression/docs/function/probability/random.js index 46a7d6049..70ec2ed32 100644 --- a/lib/expression/docs/function/probability/random.js +++ b/lib/expression/docs/function/probability/random.js @@ -16,5 +16,5 @@ module.exports = { 'random(10, 20)', 'random([2, 3])' ], - 'seealso': ['pickRandom', 'randomInt'] + 'seealso': ['pickRandom', 'pickMultipleRandom', 'randomInt'] }; diff --git a/lib/expression/docs/function/probability/randomInt.js b/lib/expression/docs/function/probability/randomInt.js index 3da4aceac..0164d5a41 100644 --- a/lib/expression/docs/function/probability/randomInt.js +++ b/lib/expression/docs/function/probability/randomInt.js @@ -14,5 +14,5 @@ module.exports = { 'randInt(10, 20)', 'randInt([2, 3], 10)' ], - 'seealso': ['pickRandom', 'random'] + 'seealso': ['pickRandom', 'pickMultipleRandom', 'random'] }; \ No newline at end of file diff --git a/lib/expression/docs/index.js b/lib/expression/docs/index.js index 8dab7f890..410d097d2 100644 --- a/lib/expression/docs/index.js +++ b/lib/expression/docs/index.js @@ -207,6 +207,7 @@ function factory (construction, config, load, typed) { docs.multinomial = require('./function/probability/multinomial'); docs.permutations = require('./function/probability/permutations'); docs.pickRandom = require('./function/probability/pickRandom'); + docs.pickMultipleRandom = require('./function/probability/pickMultipleRandom'); docs.random = require('./function/probability/random'); docs.randomInt = require('./function/probability/randomInt'); diff --git a/lib/function/probability/distribution.js b/lib/function/probability/distribution.js index eae9c96b6..7b263be32 100644 --- a/lib/function/probability/distribution.js +++ b/lib/function/probability/distribution.js @@ -24,13 +24,14 @@ function factory (type, config, load, typed) { * * See also: * - * random, randomInt, pickRandom + * random, randomInt, pickRandom, pickMultipleRandom * * @param {string} name Name of a distribution. Choose from 'uniform', 'normal'. * @return {Object} Returns a distribution object containing functions: * `random([size] [, min] [, max])`, * `randomInt([min] [, max])`, - * `pickRandom(array)` + * `pickRandom(array)`, + * `pickMultipleRandom(array, number)` */ function distribution(name) { if (!distributions.hasOwnProperty(name)) @@ -142,8 +143,43 @@ function factory (type, config, load, typed) { // TODO: add support for multi dimensional matrices return possibles[Math.floor(Math.random() * possibles.length)]; - } + }, + pickMultipleRandom: function(possibles, number) { + if (arguments.length !== 2) { + throw new ArgumentsError('pickMultipleRandom', arguments.length, 2); + } + if (possibles && possibles.isMatrix === true) { + possibles = possibles.valueOf(); // get Array + } + else if (!Array.isArray(possibles)) { + throw new TypeError('Unsupported type of value in function pickMultipleRandom'); + } + + if (array.size(possibles).length > 1) { + throw new Error('Only one dimensional vectors supported'); + } + + var length = possibles.length; + + if (length == 0) { + return []; + } else if (number >= length) { + return possibles; + } + + var result = []; + var pick; + + while (result.length < number) { + pick = possibles[Math.floor(Math.random() * length)]; + if (result.indexOf(pick) == -1) { + result.push(pick); + } + } + + return result; + } }; var _random = function(min, max) { diff --git a/lib/function/probability/index.js b/lib/function/probability/index.js index 91783721e..91a445bdc 100644 --- a/lib/function/probability/index.js +++ b/lib/function/probability/index.js @@ -7,6 +7,7 @@ module.exports = [ require('./multinomial'), require('./permutations'), require('./pickRandom'), + require('./pickMultipleRandom'), require('./random'), require('./randomInt') ]; diff --git a/lib/function/probability/pickMultipleRandom.js b/lib/function/probability/pickMultipleRandom.js new file mode 100644 index 000000000..3e8d636bb --- /dev/null +++ b/lib/function/probability/pickMultipleRandom.js @@ -0,0 +1,34 @@ +'use strict'; + +function factory (type, config, load, typed) { + var distribution = load(require('./distribution')); + + /** + * Random pick a specified number of values from a one dimensional array. + * Array elements are picked using a random function with uniform distribution. + * + * Syntax: + * + * math.pickMultipleRandom(array, int) + * + * Examples: + * + * math.pickMultipleRandom([3, 6, 12, 2], 2); // returns two of the values in the array + * + * See also: + * + * random, randomInt, pickRandom, pickMultipleRandom + * + * @param {Array} array A one dimensional array + * @param {number} num A one dimensional array + * @return {array} An array with n elements of the provided input array + */ + var pickMultipleRandom = distribution('uniform').pickMultipleRandom; + + pickMultipleRandom.toTex = undefined; // use default template + + return pickMultipleRandom; +} + +exports.name = 'pickMultipleRandom'; +exports.factory = factory; diff --git a/lib/function/probability/random.js b/lib/function/probability/random.js index e923ed922..93b83fcb1 100644 --- a/lib/function/probability/random.js +++ b/lib/function/probability/random.js @@ -25,7 +25,7 @@ function factory (type, config, load, typed) { * * See also: * - * randomInt, pickRandom + * randomInt, pickRandom, pickMultipleRandom * * @param {Array | Matrix} [size] If provided, an array or matrix with given * size and filled with random values is returned diff --git a/lib/function/probability/randomInt.js b/lib/function/probability/randomInt.js index b6ff0818c..613a05a74 100644 --- a/lib/function/probability/randomInt.js +++ b/lib/function/probability/randomInt.js @@ -23,7 +23,7 @@ function factory (type, config, load, typed) { * * See also: * - * random, pickRandom + * random, pickRandom, pickMultipleRandom * * @param {Array | Matrix} [size] If provided, an array or matrix with given * size and filled with random values is returned diff --git a/test/function/probability/distribution.test.js b/test/function/probability/distribution.test.js index 9e0fa7186..6fcd8f785 100644 --- a/test/function/probability/distribution.test.js +++ b/test/function/probability/distribution.test.js @@ -282,6 +282,91 @@ describe('distribution', function () { }); }); + describe('pickMultipleRandom', function() { + + it('should pick the given number of values from the given array', function() { + var possibles = [11, 22, 33, 44, 55], + number = 3, + picked = []; + + assert.equal(uniformDistrib.pickMultipleRandom(possibles, number).length, number); + }); + + it('should return the given array if the given number is equal its length', function() { + var possibles = [11, 22, 33, 44, 55], + number = 5, + picked = []; + + assert.equal(uniformDistrib.pickMultipleRandom(possibles, number), possibles); + }); + + it('should return the given array if the given number is greater than its length', function() { + var possibles = [11, 22, 33, 44, 55], + number = 6, + picked = []; + + assert.equal(uniformDistrib.pickMultipleRandom(possibles, number), possibles); + }); + + it('should pick numbers from the given array following an uniform distribution', function() { + var possibles = [11, 22, 33, 44, 55], + number = 2, + picked = [], + count; + + _.times(1000, function() { + picked.push(uniformDistrib.pickMultipleRandom(possibles, number)); + }); + + count = _.filter(picked, function(val) { return val.indexOf(11) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(22) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(33) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(44) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(55) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + }); + + it('should pick numbers from the given matrix following an uniform distribution', function() { + var possibles = math.matrix([11, 22, 33, 44, 55]), + number = 3, + picked = [], + count; + + _.times(1000, function() { + picked.push(uniformDistrib.pickMultipleRandom(possibles, number)); + }); + + count = _.filter(picked, function(val) { return val.indexOf(11) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(22) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(33) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(44) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + + count = _.filter(picked, function(val) { return val.indexOf(55) !== -1 }).length; + assert.equal(math.round(count/(picked.length*number), 1), 0.2); + }); + + it('should throw an error when providing a multi dimensional matrix', function() { + assert.throws(function () { + uniformDistrib.pickMultipleRandom(math.matrix([[1,2], [3,4]]), 2); + }, /Only one dimensional vectors supported/); + }); + }); + describe('distribution.normal', function() { it('should pick numbers in [0, 1] following a normal distribution', function() { diff --git a/test/function/probability/pickMultipleRandom.test.js b/test/function/probability/pickMultipleRandom.test.js new file mode 100644 index 000000000..bf196f0d4 --- /dev/null +++ b/test/function/probability/pickMultipleRandom.test.js @@ -0,0 +1,17 @@ +var assert = require('assert'), + math = require('../../../index'); + +describe('pickMultipleRandom', function () { + // Note: pickMultipleRandom is a convenience function generated by distribution + // it is tested in distribution.test.js + + it('should have a function pickMultipleRandom', function () { + assert.equal(typeof math.pickMultipleRandom, 'function'); + }) + + it('should LaTeX pickMultipleRandom', function () { + var expression = math.parse('pickMultipleRandom([1,2,3], 2)'); + assert.equal(expression.toTex(), '\\mathrm{pickMultipleRandom}\\left(\\begin{bmatrix}1\\\\2\\\\3\\\\\\end{bmatrix},2\\right)'); + }); + +});