mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif
142 lines (117 loc) • 4.45 kB
JavaScript
import { factory } from '../../utils/factory'
import { isNumber } from '../../utils/is'
import { arraySize } from '../../utils/array'
import { createRng } from './util/seededRNG'
const name = 'pickRandom'
const dependencies = ['typed', 'config', '?on']
export const createPickRandom = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, on }) => {
// seeded pseudo random number generator
let rng = createRng(config.randomSeed)
if (on) {
on('config', function (curr, prev) {
if (curr.randomSeed !== prev.randomSeed) {
rng = createRng(curr.randomSeed)
}
})
}
/**
* Random pick one or more values from a one dimensional array.
* Array elements are picked using a random function with uniform or weighted distribution.
*
* Syntax:
*
* math.pickRandom(array)
* math.pickRandom(array, number)
* math.pickRandom(array, weights)
* math.pickRandom(array, number, weights)
* math.pickRandom(array, weights, number)
*
* Examples:
*
* math.pickRandom([3, 6, 12, 2]) // returns one of the values in the array
* math.pickRandom([3, 6, 12, 2], 2) // returns an array of two of the values in the array
* math.pickRandom([3, 6, 12, 2], [1, 3, 2, 1]) // returns one of the values in the array with weighted distribution
* math.pickRandom([3, 6, 12, 2], 2, [1, 3, 2, 1]) // returns an array of two of the values in the array with weighted distribution
* math.pickRandom([3, 6, 12, 2], [1, 3, 2, 1], 2) // returns an array of two of the values in the array with weighted distribution
*
* See also:
*
* random, randomInt
*
* @param {Array | Matrix} array A one dimensional array
* @param {Int} number An int or float
* @param {Array | Matrix} weights An array of ints or floats
* @return {number | Array} Returns a single random value from array when number is 1 or undefined.
* Returns an array with the configured number of elements when number is > 1.
*/
return typed({
'Array | Matrix': function (possibles) {
return _pickRandom(possibles)
},
'Array | Matrix, number': function (possibles, number) {
return _pickRandom(possibles, number, undefined)
},
'Array | Matrix, Array': function (possibles, weights) {
return _pickRandom(possibles, undefined, weights)
},
'Array | Matrix, Array | Matrix, number': function (possibles, weights, number) {
return _pickRandom(possibles, number, weights)
},
'Array | Matrix, number, Array | Matrix': function (possibles, number, weights) {
return _pickRandom(possibles, number, weights)
}
})
function _pickRandom (possibles, number, weights) {
const single = (typeof number === 'undefined')
if (single) {
number = 1
}
possibles = possibles.valueOf() // get Array
if (weights) {
weights = weights.valueOf() // get Array
}
if (arraySize(possibles).length > 1) {
throw new Error('Only one dimensional vectors supported')
}
let totalWeights = 0
if (typeof weights !== 'undefined') {
if (weights.length !== possibles.length) {
throw new Error('Weights must have the same length as possibles')
}
for (let i = 0, len = weights.length; i < len; i++) {
if (!isNumber(weights[i]) || weights[i] < 0) {
throw new Error('Weights must be an array of positive numbers')
}
totalWeights += weights[i]
}
}
const length = possibles.length
if (length === 0) {
return []
} else if (number >= length) {
return number > 1 ? possibles : possibles[0]
}
const result = []
let pick
while (result.length < number) {
if (typeof weights === 'undefined') {
pick = possibles[Math.floor(rng() * length)]
} else {
let randKey = rng() * totalWeights
for (let i = 0, len = possibles.length; i < len; i++) {
randKey -= weights[i]
if (randKey < 0) {
pick = possibles[i]
break
}
}
}
if (result.indexOf(pick) === -1) {
result.push(pick)
}
}
return single ? result[0] : result
// TODO: return matrix when input was a matrix
// TODO: add support for multi dimensional matrices
}
})