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; }, {}) ; };