From dd243a4e235b29a70a61e49069b811928ecc5d33 Mon Sep 17 00:00:00 2001 From: Richard King Date: Wed, 23 Aug 2023 12:08:12 +0200 Subject: [PATCH] feat(weighted): initial release --- packages/weighted/src/RandomWeighted/index.ts | 69 +++++++++++++++++++ packages/weighted/src/index.ts | 7 ++ .../test/RandomWeighted/index.test.ts | 69 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 packages/weighted/src/RandomWeighted/index.ts create mode 100644 packages/weighted/src/index.ts create mode 100644 packages/weighted/test/RandomWeighted/index.test.ts diff --git a/packages/weighted/src/RandomWeighted/index.ts b/packages/weighted/src/RandomWeighted/index.ts new file mode 100644 index 0000000..c7ceab2 --- /dev/null +++ b/packages/weighted/src/RandomWeighted/index.ts @@ -0,0 +1,69 @@ +import { RandomGenerator } from '@grandom/core' + +export default class RandomWeighted extends RandomGenerator { + /** + * Returns a value according the weights. + * + * @param weightsWithValues An array of weight-value pairs. + */ + weighted > (weightsWithValues: T): T[number][1] + + /** + * Returns a value according the weights. + * + * @param values An array of values. + * @param weights An array of weights. + */ + weighted (values: T[], weights: number[]): T + + weighted (arg1: any, arg2?: any): any { + if (typeof arg1 !== 'undefined') { + const weights: number[] = [] + const elements: any[] = [] + + // process weightsWithValues ----------------------------------------------------------------- + if (typeof arg2 === 'undefined') { + if (!Array.isArray(arg1)) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`weightsWithValues must be an array, got: ${arg1} (typeof === '${typeof arg1}').`) + } + + if (arg1.length < 1) { + return + } + + for (const weightWithValue of arg1) { + const weight = weightWithValue[0] + + if (!(1 in weightWithValue)) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`value doesn't exist in ${weightWithValue}`) + } + + const value = weightWithValue[1] + + weights.push(weight) + elements.push(value) + } + + return this._engine.nextWeighted(elements, weights) + + // process values, and weights --------------------------------------------------------------- + } else { + if (arg1.length < 1) { + return + } + + for (let i = 0; i < arg1.length; i++) { + const value = arg1[i] + const weight = arg2[i] + + weights.push(weight) + elements.push(value) + } + + return this._engine.nextWeighted(elements, weights) + } + } + } +} diff --git a/packages/weighted/src/index.ts b/packages/weighted/src/index.ts new file mode 100644 index 0000000..bacb7cb --- /dev/null +++ b/packages/weighted/src/index.ts @@ -0,0 +1,7 @@ +import BasicEngine from '@grandom/engines/basic' +import RandomWeighted from './RandomWeighted' + +const random = new RandomWeighted(new BasicEngine()) +const weighted = random.weighted.bind(random) + +export default weighted diff --git a/packages/weighted/test/RandomWeighted/index.test.ts b/packages/weighted/test/RandomWeighted/index.test.ts new file mode 100644 index 0000000..8b269ef --- /dev/null +++ b/packages/weighted/test/RandomWeighted/index.test.ts @@ -0,0 +1,69 @@ +import { BasicEngine } from '@grandom/engines' +import RandomWeighted from '../../src/RandomWeighted' + +const random = new RandomWeighted(new BasicEngine()) + +describe('RandomWeighted', () => { + describe('.weighted()', () => { + describe('weight-value pair array', () => { + test('empty weight-value array', () => { + expect(random.weighted([])).toBeUndefined() + }) + + test('weight-value array of length === 1', () => { + for (let i = 0; i < 10_000; i++) { + expect(random.weighted([[1, 'a']])).toBe('a') + } + }) + + test('weight-value array of length === 2', () => { + for (let i = 0; i < 10_000; i++) { + expect(random.weighted([ + [1, 'a'], + [2, 3] + ])).toBeOneOf(['a', 3]) + } + }) + + test('weight-value array of length === 3', () => { + for (let i = 0; i < 10_000; i++) { + expect(random.weighted([ + [1, 'a'], + [2, 3], + [3, false] + ])).toBeOneOf(['a', 3, false]) + } + }) + }) + + describe('values array, weights array', () => { + test('empty values, empty weights', () => { + expect(random.weighted([], [])).toBeUndefined() + }) + + test('1 pair of value-weight', () => { + for (let i = 0; i < 10_000; i++) { + expect(random.weighted(['a'], [1])).toBe('a') + } + }) + + test('2 pairs of value-weight', () => { + for (let i = 0; i < 10_000; i++) { + expect(random.weighted( + ['a', 3], + [1, 2] + )).toBeOneOf(['a', 3]) + } + }) + + test('3 pairs of value-weight', () => { + for (let i = 0; i < 10_000; i++) { + expect(random.weighted( + ['a', 3, false], + [1, 2, 3] + )).toBeOneOf(['a', 3, false]) + } + }) + }) + }) +})