Skip to content

Commit 2bda9de

Browse files
authored
Merge branch 'next' into 1349-location-nearbyGPSCoordinate-options
2 parents a68f61e + 59824e6 commit 2bda9de

File tree

6 files changed

+166
-3
lines changed

6 files changed

+166
-3
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Faker - Copyright (c) 2022
1+
Faker - Copyright (c) 2022-2023
22

33
This software consists of voluntary contributions made by many individuals.
44
For exact contribution history, see the revision history

src/internal/mersenne/twister.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2022 Faker
2+
* Copyright (c) 2022-2023 Faker
33
*
44
* This is a version of the original source code migrated to TypeScript and
55
* modified by the Faker team.

src/modules/helpers/index.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,55 @@ export class HelpersModule {
459459
return array[index];
460460
}
461461

462+
/**
463+
* Returns a weighted random element from the given array. Each element of the array should be an object with two keys `weight` and `value`.
464+
*
465+
* - Each `weight` key should be a number representing the probability of selecting the value, relative to the sum of the weights. Weights can be any positive float or integer.
466+
* - Each `value` key should be the corresponding value.
467+
*
468+
* For example, if there are two values A and B, with weights 1 and 2 respectively, then the probability of picking A is 1/3 and the probability of picking B is 2/3.
469+
*
470+
* @template T The type of the entries to pick from.
471+
* @param array Array to pick the value from.
472+
*
473+
* @example
474+
* faker.helpers.weightedArrayElement([{ weight: 5, value: 'sunny' }, { weight: 4, value: 'rainy' }, { weight: 1, value: 'snowy' }]) // 'sunny', 50% of the time, 'rainy' 40% of the time, 'snowy' 10% of the time
475+
*
476+
* @since 8.0.0
477+
*/
478+
weightedArrayElement<T>(
479+
array: ReadonlyArray<{ weight: number; value: T }>
480+
): T {
481+
if (array.length === 0) {
482+
throw new FakerError(
483+
'weightedArrayElement expects an array with at least one element'
484+
);
485+
}
486+
487+
if (!array.every((elt) => elt.weight > 0)) {
488+
throw new FakerError(
489+
'weightedArrayElement expects an array of { weight, value } objects where weight is a positive number'
490+
);
491+
}
492+
493+
const total = array.reduce((acc, { weight }) => acc + weight, 0);
494+
const random = this.faker.number.float({
495+
min: 0,
496+
max: total,
497+
precision: 1e-9,
498+
});
499+
let current = 0;
500+
for (const { weight, value } of array) {
501+
current += weight;
502+
if (random < current) {
503+
return value;
504+
}
505+
}
506+
507+
// In case of rounding errors, return the last element
508+
return array[array.length - 1].value;
509+
}
510+
462511
/**
463512
* Returns a subset with random elements of the given array in random order.
464513
*

src/modules/internet/user-agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2022 Faker
2+
* Copyright (c) 2022-2023 Faker
33
*
44
* This is a version of the original code migrated to TypeScript and modified
55
* by the Faker team.

test/__snapshots__/helpers.spec.ts.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ exports[`helpers > 42 > uniqueArray > with array 1`] = `
177177
]
178178
`;
179179

180+
exports[`helpers > 42 > weightedArrayElement > with array 1`] = `"sunny"`;
181+
182+
exports[`helpers > 42 > weightedArrayElement > with array with percentages 1`] = `"sunny"`;
183+
180184
exports[`helpers > 1211 > arrayElement > noArgs 1`] = `"c"`;
181185

182186
exports[`helpers > 1211 > arrayElement > with array 1`] = `"!"`;
@@ -368,6 +372,10 @@ exports[`helpers > 1211 > uniqueArray > with array 1`] = `
368372
]
369373
`;
370374

375+
exports[`helpers > 1211 > weightedArrayElement > with array 1`] = `"snowy"`;
376+
377+
exports[`helpers > 1211 > weightedArrayElement > with array with percentages 1`] = `"snowy"`;
378+
371379
exports[`helpers > 1337 > arrayElement > noArgs 1`] = `"a"`;
372380

373381
exports[`helpers > 1337 > arrayElement > with array 1`] = `"l"`;
@@ -541,3 +549,7 @@ exports[`helpers > 1337 > uniqueArray > with array 1`] = `
541549
"d",
542550
]
543551
`;
552+
553+
exports[`helpers > 1337 > weightedArrayElement > with array 1`] = `"sunny"`;
554+
555+
exports[`helpers > 1337 > weightedArrayElement > with array with percentages 1`] = `"sunny"`;

test/helpers.spec.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ describe('helpers', () => {
5959
t.it('noArgs').it('with array', 'Hello World!'.split(''));
6060
});
6161

62+
t.describe('weightedArrayElement', (t) => {
63+
t.it('with array', [
64+
{ weight: 5, value: 'sunny' },
65+
{ weight: 4, value: 'rainy' },
66+
{ weight: 1, value: 'snowy' },
67+
]);
68+
69+
t.it('with array with percentages', [
70+
{ weight: 0.5, value: 'sunny' },
71+
{ weight: 0.4, value: 'rainy' },
72+
{ weight: 0.1, value: 'snowy' },
73+
]);
74+
});
75+
6276
t.describe('arrayElements', (t) => {
6377
t.it('noArgs')
6478
.it('with array', 'Hello World!'.split(''))
@@ -145,6 +159,94 @@ describe('helpers', () => {
145159
});
146160
});
147161

162+
describe('weightedArrayElement', () => {
163+
it('should return a weighted random element in the array', () => {
164+
const testArray = [
165+
{ weight: 10, value: 'hello' },
166+
{ weight: 5, value: 'to' },
167+
{ weight: 3, value: 'you' },
168+
{ weight: 2, value: 'my' },
169+
{ weight: 1, value: 'friend' },
170+
];
171+
const actual = faker.helpers.weightedArrayElement(testArray);
172+
173+
expect(testArray.map((a) => a.value)).toContain(actual);
174+
});
175+
176+
it('should return a weighted random element in the array using floats', () => {
177+
const testArray = [
178+
{ weight: 0.1, value: 'hello' },
179+
{ weight: 0.05, value: 'to' },
180+
{ weight: 0.03, value: 'you' },
181+
{ weight: 0.02, value: 'my' },
182+
{ weight: 0.01, value: 'friend' },
183+
];
184+
const actual = faker.helpers.weightedArrayElement(testArray);
185+
186+
expect(testArray.map((a) => a.value)).toContain(actual);
187+
});
188+
189+
it('should return the only element in the array when there is only 1', () => {
190+
const testArray = [{ weight: 10, value: 'hello' }];
191+
const actual = faker.helpers.weightedArrayElement(testArray);
192+
193+
expect(actual).toBe('hello');
194+
});
195+
196+
it('should throw if the array is empty', () => {
197+
expect(() => faker.helpers.weightedArrayElement([])).toThrowError(
198+
new FakerError(
199+
'weightedArrayElement expects an array with at least one element'
200+
)
201+
);
202+
});
203+
204+
it('should allow falsey values', () => {
205+
const testArray = [{ weight: 1, value: false }];
206+
const actual = faker.helpers.weightedArrayElement(testArray);
207+
expect(actual).toBe(false);
208+
});
209+
210+
it('should throw if any weight is zero', () => {
211+
const testArray = [
212+
{ weight: 0, value: 'hello' },
213+
{ weight: 5, value: 'to' },
214+
];
215+
expect(() =>
216+
faker.helpers.weightedArrayElement(testArray)
217+
).toThrowError(
218+
new FakerError(
219+
'weightedArrayElement expects an array of { weight, value } objects where weight is a positive number'
220+
)
221+
);
222+
});
223+
224+
it('should throw if any weight is negative', () => {
225+
const testArray = [
226+
{ weight: -1, value: 'hello' },
227+
{ weight: 5, value: 'to' },
228+
];
229+
expect(() =>
230+
faker.helpers.weightedArrayElement(testArray)
231+
).toThrowError(
232+
new FakerError(
233+
'weightedArrayElement expects an array of { weight, value } objects where weight is a positive number'
234+
)
235+
);
236+
});
237+
238+
it('should not throw with a frozen array', () => {
239+
const testArray = [
240+
{ weight: 7, value: 'ice' },
241+
{ weight: 3, value: 'snow' },
242+
];
243+
const frozenArray = Object.freeze(testArray);
244+
expect(() =>
245+
faker.helpers.weightedArrayElement(frozenArray)
246+
).to.not.throw();
247+
});
248+
});
249+
148250
describe('arrayElements', () => {
149251
it('should return a subset with random elements in the array', () => {
150252
const testArray = ['hello', 'to', 'you', 'my', 'friend'];

0 commit comments

Comments
 (0)