100 lines
2.6 KiB
JavaScript
100 lines
2.6 KiB
JavaScript
var exports = module.exports = function (xs) {
|
|
if (typeof xs !== 'object') { // of which Arrays are
|
|
throw new TypeError('Must be an Array or an object');
|
|
}
|
|
|
|
return Object.keys(exports).reduce(function (acc, name) {
|
|
acc[name] = exports[name].bind(null, xs);
|
|
return acc;
|
|
}, {});
|
|
};
|
|
|
|
exports.shuffle = function (xs) {
|
|
if (Array.isArray(xs)) {
|
|
// uniform shuffle
|
|
var res = xs.slice();
|
|
for (var i = res.length - 1; i >= 0; i--) {
|
|
var n = Math.floor(Math.random() * i);
|
|
var t = res[i];
|
|
res[i] = res[n];
|
|
res[n] = t;
|
|
}
|
|
return res;
|
|
}
|
|
else if (typeof xs === 'object') {
|
|
// weighted shuffle
|
|
var weights = Object.keys(xs).reduce(function (acc, key) {
|
|
acc[key] = xs[key];
|
|
return acc;
|
|
}, {});
|
|
|
|
var ret = [];
|
|
|
|
while (Object.keys(weights).length > 0) {
|
|
var key = exports.pick(weights);
|
|
delete weights[key];
|
|
ret.push(key);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
else {
|
|
throw new TypeError('Must be an Array or an object');
|
|
}
|
|
};
|
|
|
|
exports.pick = function (xs) {
|
|
if (Array.isArray(xs)) {
|
|
// uniform sample
|
|
return xs[Math.floor(Math.random() * xs.length)];
|
|
}
|
|
else if (typeof xs === 'object') {
|
|
// weighted sample
|
|
var weights = exports.normalize(xs);
|
|
if (!weights) return undefined;
|
|
|
|
var n = Math.random();
|
|
var threshold = 0;
|
|
var keys = Object.keys(weights);
|
|
|
|
for (var i = 0; i < keys.length; i++) {
|
|
threshold += weights[keys[i]];
|
|
if (n < threshold) return keys[i];
|
|
}
|
|
throw new Error('Exceeded threshold. Something is very wrong.');
|
|
}
|
|
else {
|
|
throw new TypeError('Must be an Array or an object');
|
|
}
|
|
};
|
|
|
|
exports.normalize = function (weights) {
|
|
if (typeof weights !== 'object' || Array.isArray(weights)) {
|
|
throw 'Not an object'
|
|
}
|
|
|
|
var keys = Object.keys(weights);
|
|
if (keys.length === 0) return undefined;
|
|
|
|
var total = keys.reduce(function (sum, key) {
|
|
var x = weights[key];
|
|
if (x < 0) {
|
|
throw new Error('Negative weight encountered at key ' + key);
|
|
}
|
|
else if (typeof x !== 'number') {
|
|
throw new TypeError('Number expected, got ' + typeof x);
|
|
}
|
|
else {
|
|
return sum + x;
|
|
}
|
|
}, 0);
|
|
|
|
return total === 1
|
|
? weights
|
|
: keys.reduce(function (acc, key) {
|
|
acc[key] = weights[key] / total;
|
|
return acc;
|
|
}, {})
|
|
;
|
|
};
|