UNPKG

hasard

Version:

Random variables and random nested objects manipulation in javascript

608 lines (540 loc) 14.2 kB
/* eslint no-new: "off" */ /* eslint ava/prefer-async-await: "off" */ const test = require('ava'); const testDistribution = require('./helpers/test-distribution'); const hasard = require('..'); test('hasard.Value(Array.<Any>)', t => { const values = ['white', 'yellow']; return testDistribution(t, new hasard.Value(values), (t, a) => { t.not(values.indexOf(a), -1); }, (t, as) => { const counts = values.map(s => as.filter(v => v === s).length / as.length); const average = 1 / values.length; const threshold = 1 / Math.sqrt(as.length); counts.forEach(c => { t.true(Math.abs(c - average) < threshold); }); } ); }); test('hasard.Boolean(Number)', t => { const p = 0.2; return testDistribution(t, new hasard.Boolean(p), (t, a) => { t.is(typeof (a), 'boolean'); }, (t, as) => { const counts = [true, false].map(s => as.filter(v => v === s).length / as.length); const expected = [p, 1 - p]; const threshold = 1 / Math.sqrt(as.length); counts.forEach((c, index) => { t.true(Math.abs(c - expected[index]) < threshold); }); } ); }); test('hasard.Value(Object)', t => { const opts = { choices: ['white', 'yellow'], weights: [0.75, 0.25] }; return testDistribution(t, new hasard.Value(opts), (t, a) => { t.not(opts.choices.indexOf(a), -1); }, (t, as) => { const counts = opts.choices.map(s => as.filter(v => v === s).length / as.length); const expected = opts.weights; const threshold = 1 / Math.sqrt(as.length); counts.forEach((c, index) => { t.true(Math.abs(c - expected[index]) < threshold); }); } ); }); test('hasard.Number(Array.<Number>)', t => { t.throws(() => { new hasard.Number([1]); }, 'invalid array, range array length must be 2'); t.throws(() => { new hasard.Number([0, 1, 2]); }, 'invalid array, range array length must be 2'); const range = [10, 15]; const splits = [10, 11, 12, 13, 14]; return testDistribution(t, new hasard.Number(range), (t, a) => { t.is(typeof (a), 'number'); t.true(a >= range[0]); t.true(a < range[1]); }, (t, as) => { const counts = splits.map(n => as.filter(v => n <= v && v < n + 1).length / as.length); const average = 1 / counts.length; const threshold = 1 / Math.sqrt(as.length); counts.forEach(c => { t.true(Math.abs(c - average) < threshold); }); } ); }); test('hasard.Number(start, end)', t => { const range = [10, 15]; const splits = [10, 11, 12, 13, 14]; return testDistribution(t, new hasard.Number(range[0], range[1]), (t, a) => { t.is(typeof (a), 'number'); t.true(a >= range[0]); t.true(a < range[1]); }, (t, as) => { const counts = splits.map(n => as.filter(v => n <= v && v < n + 1).length / as.length); const average = 1 / counts.length; const threshold = 1 / Math.sqrt(as.length); counts.forEach(c => { t.true(Math.abs(c - average) < threshold); }); } ); }); test('hasard.Number(Object)', t => { const uniform = { type: 'uniform', start: 22, end: 22.2 }; return testDistribution(t, new hasard.Number(uniform), (t, a) => { t.is(typeof (a), 'number'); t.true(a >= uniform.start); t.true(a < uniform.end); }, (t, as) => { const nSplit = 5; const splits = new Array(nSplit).fill(1).map((_, index) => { return ((uniform.end - uniform.start) * index / nSplit) + uniform.start; }); const step = splits[1] - splits[0]; const counts = splits.map(n => as.filter(v => n <= v && v < n + step).length / as.length); const average = 1 / counts.length; const threshold = 1 / Math.sqrt(as.length); counts.forEach(c => { t.true(Math.abs(c - average) < threshold); }); } ).then(() => { const normal = { type: 'normal', mean: -2, std: 3 }; return testDistribution(t, new hasard.Number(normal), (t, a) => { t.is(typeof (a), 'number'); }, (t, as) => { const sum = as.reduce((a, b) => a + b, 0); const average = sum / as.length; const variance = as.map(a => (a - average) * (a - average)).reduce((a, b) => a + b, 0) / as.length; const threshold = 100 / Math.sqrt(as.length); t.true(Math.abs(normal.mean - average) < threshold); t.true(Math.abs(variance - (normal.std * normal.std)) < threshold); } ); }) .then(() => { const mean = -2; const std = 3; const truncatedNormal = { type: 'truncated-normal', mean, std }; return testDistribution(t, new hasard.Number(truncatedNormal), (t, a) => { t.is(typeof (a), 'number'); t.true(a >= mean - (2 * std)); t.true(a < mean + (2 * std)); }, (t, as) => { const sum = as.reduce((a, b) => a + b, 0); const average = sum / as.length; const variance = as.map(a => (a - average) * (a - average)).reduce((a, b) => a + b, 0) / as.length; const threshold = 100 / Math.sqrt(as.length); t.true(Math.abs(truncatedNormal.mean - average) < threshold); t.true(Math.abs(variance - (truncatedNormal.std * truncatedNormal.std)) < threshold); } ); }); }); test('hasard.Integer([start, end])', t => { t.throws(() => { new hasard.Integer([1]); }, '1 must be a length-2 array'); t.throws(() => { new hasard.Integer([0, 1, 2]); }, '0,1,2 must be a length-2 array'); t.throws(() => { new hasard.Integer([0.1, 3]); }, 'start (0.1) must be an integer'); t.throws(() => { new hasard.Integer([0, 3.1]); }, 'end (3.1) must be an integer'); const range = [10, 15]; return testDistribution(t, new hasard.Integer(range), (t, a) => { t.is(typeof (a), 'number'); t.is(a, Math.floor(a)); t.true(a >= range[0]); t.true(a <= range[1]); }, (t, as) => { const values = new Array(range[1] - range[0] + 1).fill(1).map((_, i) => i + range[0]); const counts = values.map(n => as.filter(v => v === n).length / as.length); const average = 1 / values.length; const threshold = 1 / Math.sqrt(as.length); counts.forEach(c => { t.true(Math.abs(c - average) < threshold); }); } ); }); test('hasard.Integer(start, end)', t => { const range = [10, 15]; return testDistribution(t, new hasard.Integer(range[0], range[1]), (t, a) => { t.is(typeof (a), 'number'); t.is(a, Math.floor(a)); t.true(a >= range[0]); t.true(a <= range[1]); }, (t, as) => { const values = new Array(range[1] - range[0] + 1).fill(1).map((_, i) => i + range[0]); const counts = values.map(n => as.filter(v => v === n).length / as.length); const average = 1 / values.length; const threshold = 1 / Math.sqrt(as.length); counts.forEach(c => { t.true(Math.abs(c - average) < threshold); }); } ); }); test('hasard.Integer({type, ...})', t => { const poisson = { type: 'poisson', lambda: 3 }; return testDistribution(t, new hasard.Integer(poisson), (t, a) => { t.is(typeof (a), 'number'); t.true(a >= 0); }, (t, as) => { const sum = as.reduce((a, b) => a + b, 0); const average = sum / as.length; const threshold = 100 / Math.sqrt(as.length); t.true(Math.abs(poisson.lambda - average) < threshold); } ); }); test('hasard.String({value, size})', t => { const range = [5, 10]; const chars = ['a', 'b', 'c', 'd']; const value = new hasard.Value(chars); const size = new hasard.Integer(range); return testDistribution(t, new hasard.String({value, size}), (t, a) => { t.is(typeof (a), 'string'); t.true(a.length >= range[0]); t.true(a.length <= range[1]); a.split('').forEach(c => { t.not(chars.indexOf(c), -1); }); } ); }); test('hasard.Array({value, size})', t => { const range = [5, 10]; const chars = ['a', 'b', 'c', 'd']; const value = new hasard.Value(chars); const size = new hasard.Integer(range); return testDistribution(t, new hasard.Array({value, size}), (t, a) => { t.is(typeof (a), 'object'); t.true(a.length >= range[0]); t.true(a.length <= range[1]); a.forEach(c => { t.not(chars.indexOf(c), -1); }); } ); }); test('hasard.Array(<Array.<Hasard>>)', t => { const chars = ['a', 'b', 'c', 'd']; const haz = new hasard.Value(chars); const values = [haz, haz, haz]; return testDistribution(t, new hasard.Array(values), (t, a) => { t.is(typeof (a), 'object'); t.is(a.length, 3); a.forEach(c => { t.not(chars.indexOf(c), -1); }); }, (t, as) => { const allSame = as.filter(a => { return (a[0] === a[1]) && (a[1] === a[2]); }); t.true(allSame.length < as.length / 2); } ); }); test('hasard.Array({values, randomOrder})', t => { const string = 'abcdefghijklmnopqrstuvwxyz'; const values = string.split(''); const shuffle = new hasard.Boolean(); return testDistribution(t, new hasard.Array({values, shuffle}), (t, a) => { t.is(a.length, 26); }, (t, as) => { const shuffled = as.filter(a => { return a.join('') !== string; }); const threshold = 100 / Math.sqrt(as.length); t.true(Math.abs((shuffled.length / as.length) - 0.5) < threshold); } ); }); test('hasard.Array({values, size})', t => { const string = 'abcdefghijklmnopqrstuvwxyz'; const values = string.split(''); const size = 15; t.throws(() => { (new hasard.Array({values, size: 32})).runOnce(); }, 'Cannot pick 32 elements in 26-size array'); return testDistribution(t, new hasard.Array({values, size}), (t, a) => { t.is(typeof (a), 'object'); t.is(a.length, size); a.forEach((c, index) => { t.not(values.indexOf(c), -1); // Test unicity t.is(a.indexOf(c), index); }); // Order is still the same t.is(a.sort(), a); } ); }); test('hasard.Object(Object)', t => { const keys = { color1: ['white', 'yellow'], color2: ['black', 'grey'] }; const opts = {}; Object.keys(keys).forEach(k => { opts[k] = new hasard.Value(keys[k]); }); return testDistribution(t, new hasard.Object(opts), (t, a) => { Object.keys(keys).forEach(k => { t.not(keys[k].indexOf(a[k]), -1); }); } ); }); test('hasard.Object(Hasard.<Array.<String>>, Hasard)', t => { const keys = hasard.array({ value: hasard.add( new hasard.Value(['+33', '+32', '+1']), new hasard.String({ value: new hasard.Value(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']), size: 10 }) ), size: 10 }); const value = hasard.add( new hasard.Value(['Mr ', 'M ']), new hasard.Value(['Thomas ', 'Nicolas ', 'Julien ', 'Quentin ', 'Maxime ']), new hasard.Value(['DURAND', 'DUBOIS', 'LEFEBVRE', 'MOREAU', 'MOREL', 'FOURNIER']) ); t.throws(() => { (new hasard.Object(['a', 'a', 'c'], new hasard.Value(['d', 'e', 'f']))); }, 'keys must be unique (keys[1] \'a\' is duplicated)'); t.throws(() => { (new hasard.Object(['a', {b: 'b'}], new hasard.Value(['d', 'e', 'f']))).runOnce(); }, 'keys must be string array (keys[1] \'[object Object]\' should be a string)'); return testDistribution(t, new hasard.Object( keys, value ), (t, a) => { t.is(Object.keys(a).length, 10); } ); }); test('hasard.Matrix(Object)', t => { const shapeSize = [1, 4]; const shapeValues = [5, 10]; const values = [0, 255]; const opts = { value: new hasard.Integer(values), shape: new hasard.Array({ value: new hasard.Integer(shapeValues), size: new hasard.Integer(shapeSize) }) }; const testSameSize = function (a, t) { if (a.length === 0) { t.fail(); } else if (Array.isArray(a[0])) { const size = a[0].length; a.forEach(b => { t.is(b.length, size); testSameSize(b, t); }); } }; const getShape = function (a) { if (a.length === 0) { return [0]; } if (Array.isArray(a[0])) { return [a.length].concat(getShape(a[0])); } return [a.length]; }; return testDistribution(t, new hasard.Matrix(opts), (t, a) => { t.true(Array.isArray(a)); testSameSize(a, t); const shape = getShape(a); t.true(shape.length >= shapeSize[0]); t.true(shape.length <= shapeSize[1]); shape.forEach(s => { t.true(s >= shapeValues[0]); t.true(s <= shapeValues[1]); }); } ); }); test('hasard.Matrix({value: Hasard.<Array>})', t => { const opts = { value: new hasard.Array({ value: new hasard.Integer([5, 10]), size: 3 }), shape: [5, 10] }; const getShape = function (a) { if (a.length === 0) { return [0]; } if (Array.isArray(a[0])) { return [a.length].concat(getShape(a[0])); } return [a.length]; }; return testDistribution(t, new hasard.Matrix(opts), (t, a) => { const shape = getShape(a); t.deepEqual(shape, [5, 10, 3]); } ); }); test('hasard.Reference(<Hasard>) with hasard.Array(<Array>)', t => { const chars = ['a', 'b', 'c', 'd']; const haz = new hasard.Reference(new hasard.Value(chars)); const values = [haz, haz, haz]; return testDistribution(t, new hasard.Array(values), (t, a) => { t.is(typeof (a), 'object'); t.is(a.length, 3); a.forEach(c => { t.not(chars.indexOf(c), -1); }); }, (t, as) => { const allSame = as.filter(a => { return (a[0] === a[1]) && (a[1] === a[2]); }); t.is(allSame.length, as.length); const first = as[0]; const sameAsFirst = as.filter(a => { return (a[0] === first[0]); }); t.true(sameAsFirst.length < as.length / 2); } ); }); test('hasard.Reference(<Hasard>) with context and contextName', t => { const int = new hasard.Integer([0, 255]); const ref = new hasard.Reference({source: int, context: 'color'}); const color = new hasard.Array({ values: [ ref, ref, ref ], contextName: 'color' }); const size = 10; const colors = new hasard.Array({ value: color, size }); return testDistribution(t, colors, (t, a) => { a.forEach(color => { // Console.log(color) t.is(typeof (color[0]), 'number'); t.is(color[0], color[1]); t.is(color[0], color[2]); }); t.true(a.filter(color => a[0][0] === color[0]).length < size / 3); } ); }); test('hasard.fn(Function)', t => { const refA = new hasard.Reference(new hasard.Number([0, 1])); const refB = new hasard.Reference(new hasard.Number([0, 1])); const addHasard = hasard.fn((a, b) => { return a + b; }); const obj = new hasard.Object({ a: refA, b: refB, sum: addHasard(refA, refB) }); return testDistribution(t, obj, (t, {a, b, sum}) => { t.is(a + b, sum); } ); });