Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/api-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const apiPages = [
{ text: 'Faker', link: '/api/faker.html' },
{ text: 'SimpleFaker', link: '/api/simpleFaker.html' },
{ text: 'Randomizer', link: '/api/randomizer.html' },
{ text: 'Distributors', link: '/api/distributors.html' },
{ text: 'Utilities', link: '/api/utils.html' },
{
text: 'Modules',
Expand Down
2 changes: 2 additions & 0 deletions scripts/apidocs/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { RawApiDocsPage } from './processing/class';
import {
processModuleClasses,
processProjectClasses,
processProjectDistributors,
processProjectInterfaces,
processProjectUtilities,
} from './processing/class';
Expand All @@ -26,6 +27,7 @@ export function processComponents(project: Project): RawApiDocsPage[] {
return [
...processProjectClasses(project),
...processProjectInterfaces(project),
processProjectDistributors(project),
processProjectUtilities(project),
...processModuleClasses(project),
];
Expand Down
30 changes: 30 additions & 0 deletions scripts/apidocs/processing/class.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ClassDeclaration, InterfaceDeclaration, Project } from 'ts-morph';
import { wrapCode } from '../utils/markdown';
import { required, valuesForKeys } from '../utils/value-checks';
import { newProcessingError } from './error';
import type { JSDocableLikeNode } from './jsdocs';
Expand All @@ -12,6 +13,7 @@ import type { RawApiDocsMethod } from './method';
import {
processClassConstructors,
processClassMethods,
processDistributorFunctions,
processInterfaceMethods,
processUtilityFunctions,
} from './method';
Expand Down Expand Up @@ -196,6 +198,34 @@ export function processProjectUtilities(project: Project): RawApiDocsPage {
};
}

// Distributors

export function processProjectDistributors(project: Project): RawApiDocsPage {
console.log(`- Distributors`);

const distributor = required(
project
.getSourceFile('src/distributors/distributor.ts')
?.getTypeAliases()[0],
'Distributor'
);

const jsdocs = getJsDocs(distributor);
const description = `${getDescription(jsdocs)}

${wrapCode(distributor.getText().replace(/export /, ''))}`;

return {
title: 'Distributors',
camelTitle: 'distributors',
category: undefined,
deprecated: undefined,
description,
examples: getExamples(jsdocs),
methods: processDistributorFunctions(project),
};
}

// Helpers

function preparePage(
Expand Down
11 changes: 11 additions & 0 deletions scripts/apidocs/processing/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ export function processUtilityFunctions(project: Project): RawApiDocsMethod[] {
);
}

export function processDistributorFunctions(
project: Project
): RawApiDocsMethod[] {
return processMethodLikes(
Object.values(getAllFunctions(project)).filter((fn) =>
fn.getSourceFile().getFilePath().includes('/src/distributors/')
),
(f) => f.getNameOrThrow()
);
}

// Method-likes

type MethodLikeDeclaration = SignatureLikeDeclaration &
Expand Down
24 changes: 22 additions & 2 deletions scripts/apidocs/utils/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@ const htmlSanitizeOptions: sanitizeHtml.IOptions = {
'span',
'strong',
'ul',
'table',
'thead',
'tbody',
'tr',
'th',
'td',
],
allowedAttributes: {
a: ['href', 'target', 'rel'],
button: ['class', 'title'],
div: ['class'],
pre: ['class', 'v-pre', 'tabindex'],
span: ['class', 'style'],
table: ['tabindex'],
th: ['style'],
td: ['style'],
},
selfClosing: [],
};
Expand All @@ -49,6 +58,18 @@ function comparableSanitizedHtml(html: string): string {
.replaceAll(' ', '');
}

/**
* Wraps the given code in a code block.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Wraps the given code in a code block.
* Wraps the given code in a markdown code block.

*
* @param code The code to wrap.
*
* @returns The wrapped code.
*/
export function wrapCode(code: string): string {
const delimiter = '```';
return `${delimiter}ts\n${code}\n${delimiter}`;
}

/**
* Converts a Typescript code block to an HTML string and sanitizes it.
*
Expand All @@ -57,8 +78,7 @@ function comparableSanitizedHtml(html: string): string {
* @returns The converted HTML string.
*/
export function codeToHtml(code: string): string {
const delimiter = '```';
return mdToHtml(`${delimiter}ts\n${code}\n${delimiter}`);
return mdToHtml(wrapCode(code));
}

/**
Expand Down
35 changes: 35 additions & 0 deletions src/distributors/distributor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Randomizer } from '../randomizer';

/**
* A function that determines the distribution of generated values.
* Values generated by a randomizer are considered uniformly distributed, distributor functions can be used to change this.
* If many results are collected the results form a limited distribution between `0` and `1`.
* So an exponential distributor will values resemble a limited exponential distribution.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* So an exponential distributor will values resemble a limited exponential distribution.
* So an exponential distributor's values will resemble a limited exponential distribution.

*
* Common examples of distributor functions are:
*
* - Uniform distributor: All values have the same likelihood.
* - Normal distributor: Values are more likely to be close to a specific value.
* - Exponential distributor: Values are more likely to be close to 0.
*
* Distributor functions can be used by some faker functions such as `faker.number.int()` and `faker.number.float()`.
*
* Please note that the result from the distributor function is processed further by the function accepting it.
* E.g. a distributor result of `0.5` within a call to `faker.number.int({ min: 10, max: 20 })` will result in `15`.
*
* @param randomizer The randomizer to use for generating values.
*
* @returns Generates a random float between 0 (inclusive) and 1 (exclusive).
*
* @example
* import { Distributor, Randomizer, faker } from '@faker-js/faker';
*
* const alwaysMin: Distributor = () => 0;
* const uniform: Distributor = (randomizer: Randomizer) => randomizer.next();
*
* faker.number.int({ min: 2, max: 10, distributor: alwaysMin }); // 2
* faker.number.int({ min: 0, max: 10, distributor: uniform }); // 5
Comment on lines +27 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exemplary calls should probably be repeated to better visualize the results:

Suggested change
* const alwaysMin: Distributor = () => 0;
* const uniform: Distributor = (randomizer: Randomizer) => randomizer.next();
*
* faker.number.int({ min: 2, max: 10, distributor: alwaysMin }); // 2
* faker.number.int({ min: 0, max: 10, distributor: uniform }); // 5
* const alwaysMin: Distributor = () => 0;
* faker.number.int({ min: 2, max: 10, distributor: alwaysMin }); // 2
* faker.number.int({ min: 2, max: 10, distributor: alwaysMin }); // 2
* faker.number.int({ min: 2, max: 10, distributor: alwaysMin }); // 2
* const uniform: Distributor = (randomizer: Randomizer) => randomizer.next();
* faker.number.int({ min: 0, max: 10, distributor: uniform }); // 5
* faker.number.int({ min: 0, max: 10, distributor: uniform }); // 2
* faker.number.int({ min: 0, max: 10, distributor: uniform }); // 9

*
* @since 9.6.0
*/
export type Distributor = (randomizer: Randomizer) => number;
122 changes: 122 additions & 0 deletions src/distributors/exponential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { FakerError } from '../errors/faker-error';
import type { Distributor } from './distributor';
import { uniformDistributor } from './uniform';

/**
* Creates a new function that generates power-law/exponentially distributed values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add a more accessible explanation what this means as well.

Suggested change
* Creates a new function that generates power-law/exponentially distributed values.
* Creates a new function that generates exponentially distributed values.
* This means that values on the edges (min or max based on configuration) are more likely to be generated.

This needs to be put in the second signature as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT this is a power law distribution and not a exponential distribution in the mathematical sense.

This means that values on one side of the of the value range are far more likely than the values on the other side of the range (depending on configuration).

Or something similar.

* This function uses `(base ** next() - 1) / (base - 1)` to spread the values.
*
* The following table shows the rough distribution of values generated using `exponentialDistributor({ base: x })`:
*
* | Result | Base 0.1 | Base 0.5 | Base 1 | Base 2 | Base 10 |
* | :-------: | -------: | -------: | -----: | -----: | ------: |
* | 0.0 - 0.1 | 4.1% | 7.4% | 10.0% | 13.8% | 27.8% |
* | 0.1 - 0.2 | 4.5% | 7.8% | 10.0% | 12.5% | 16.9% |
* | 0.2 - 0.3 | 5.0% | 8.2% | 10.0% | 11.5% | 12.1% |
* | 0.3 - 0.4 | 5.7% | 8.7% | 10.0% | 10.7% | 9.4% |
* | 0.4 - 0.5 | 6.6% | 9.3% | 10.0% | 10.0% | 7.8% |
* | 0.5 - 0.6 | 7.8% | 9.9% | 10.0% | 9.3% | 6.6% |
* | 0.6 - 0.7 | 9.4% | 10.7% | 10.0% | 8.8% | 5.7% |
* | 0.7 - 0.8 | 12.1% | 11.5% | 10.0% | 8.2% | 5.0% |
* | 0.8 - 0.9 | 16.9% | 12.6% | 10.0% | 7.8% | 4.5% |
* | 0.9 - 1.0 | 27.9% | 13.8% | 10.0% | 7.5% | 4.1% |
*
* The following table shows the rough distribution of values generated using `exponentialDistributor({ bias: x })`:
*
* | Result | Bias -9 | Bias -1 | Bias 0 | Bias 1 | Bias 9 |
* | :-------: | ------: | ------: | -----: | -----: | -----: |
* | 0.0 - 0.1 | 27.9% | 13.7% | 10.0% | 7.4% | 4.1% |
* | 0.1 - 0.2 | 16.9% | 12.5% | 10.0% | 7.8% | 4.5% |
* | 0.2 - 0.3 | 12.1% | 11.6% | 10.0% | 8.3% | 5.1% |
* | 0.3 - 0.4 | 9.5% | 10.7% | 10.0% | 8.8% | 5.7% |
* | 0.4 - 0.5 | 7.8% | 10.0% | 10.0% | 9.3% | 6.6% |
* | 0.5 - 0.6 | 6.6% | 9.3% | 10.0% | 9.9% | 7.7% |
* | 0.6 - 0.7 | 5.7% | 8.8% | 10.0% | 10.7% | 9.5% |
* | 0.7 - 0.8 | 5.0% | 8.2% | 10.0% | 11.5% | 12.1% |
* | 0.8 - 0.9 | 4.5% | 7.8% | 10.0% | 12.6% | 16.8% |
* | 0.9 - 1.0 | 4.1% | 7.4% | 10.0% | 13.7% | 27.9% |
*
* @param options The options for generating the distributor.
* @param options.base The base of the exponential distribution. Should be greater than 0. Defaults to `2`.
* The higher/more above `1` the `base`, the more likely the number will be closer to the minimum value.
* The lower/closer to zero the `base`, the more likely the number will be closer to the maximum value.
* Values of `1` will generate a uniform distributor.
* Can alternatively be configured using the `bias` option.
* @param options.bias An alternative way to specify the `base`. Also accepts values below zero. Defaults to `-1`.
* The higher/more positive the `bias`, the more likely the number will be closer to the maximum value.
* The lower/more negative the `bias`, the more likely the number will be closer to the minimum value.
* Values of `0` will generate a uniform distributor.
* Can alternatively be configured using the `base` option.
*
* @example
* import { exponentialDistributor, generateMersenne53Randomizer } from '@faker-js/faker';
*
* const randomizer = generateMersenne53Randomizer();
* const distributor = exponentialDistributor();
* distributor(randomizer) // 0.04643770898904198
* distributor(randomizer) // 0.13436127925491848
* distributor(randomizer) // 0.4202905589842396
* distributor(randomizer) // 0.5164955927828387
* distributor(randomizer) // 0.3476359433171099
*
* @since 9.6.0
*/
export function exponentialDistributor(
options?:
| {
/**
* The base of the exponential distribution. Should be greater than 0.
* The higher/more above `1` the `base`, the more likely the number will be closer to the minimum value.
* The lower/closer to zero the `base`, the more likely the number will be closer to the maximum value.
* Values of `1` will generate a uniform distribution.
* Can alternatively be configured using the `bias` option.
*
* @default 2
*/
base?: number;
}
| {
/**
* An alternative way to specify the `base`. Also accepts values below zero.
* The higher/more positive the `bias`, the more likely the number will be closer to the maximum value.
* The lower/more negative the `bias`, the more likely the number will be closer to the minimum value.
* Values of `0` will generate a uniform distribution.
* Can alternatively be configured using the `base` option.
*
* @default -1
*/
bias?: number;
}
): Distributor;
Comment on lines +64 to +90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason why we didn't made these two dedicated functions?

The advantages would be that it should be more obvious to the end user that you can not pass bias AND base at the same time. Technically this is already the way, but the signatures could more detailed examples based on the input that is possible. The signature containing the bias argument could only contain table with the bias values. Same with the base.

The disadvantage would be that we would still require a third combined signature (same as we have right now) for our documentation generation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting the options into two separate signatures is fine.
Maybe we did this for discoverability of both options or maybe this wasn't finished in the first place.

/**
* Creates a new function that generates exponentially distributed values.
* This function uses `(base ** next() - 1) / (base - 1)` to spread the values.
*
* @param options The options for generating the distributor.
* @param options.base The base of the exponential distribution. Should be greater than 0. Defaults to `2`.
* The higher/more above `1` the `base`, the more likely the number will be closer to the minimum value.
* The lower/closer to zero the `base`, the more likely the number will be closer to the maximum value.
* Values of `1` will generate a uniform distributor.
* Can alternatively be configured using the `bias` option.
* @param options.bias An alternative way to specify the `base`. Also accepts values below zero. Defaults to `-1`.
* The higher/more positive the `bias`, the more likely the number will be closer to the maximum value.
* The lower/more negative the `bias`, the more likely the number will be closer to the minimum value.
* Values of `0` will generate a uniform distributor.
* Can alternatively be configured using the `base` option.
*/
export function exponentialDistributor(
options: {
base?: number;
bias?: number;
} = {}
): Distributor {
const { bias = -1, base = bias <= 0 ? -bias + 1 : 1 / (bias + 1) } = options;

if (base === 1) {
return uniformDistributor();

Check warning on line 116 in src/distributors/exponential.ts

View check run for this annotation

Codecov / codecov/patch

src/distributors/exponential.ts#L116

Added line #L116 was not covered by tests
} else if (base <= 0) {
throw new FakerError('Base should be greater than 0.');
}

Check warning on line 119 in src/distributors/exponential.ts

View check run for this annotation

Codecov / codecov/patch

src/distributors/exponential.ts#L118-L119

Added lines #L118 - L119 were not covered by tests

return ({ next }) => (base ** next() - 1) / (base - 1);
}
41 changes: 41 additions & 0 deletions src/distributors/uniform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Distributor } from './distributor';

/**
* Creates a new function that generates uniformly distributed values.
* The likelihood of each value is the same.
*
* The following table shows the rough distribution of values generated using `uniformDistributor()`:
*
* | Result | Uniform |
* | :-------: | ------: |
* | 0.0 - 0.1 | 10.0% |
* | 0.1 - 0.2 | 10.0% |
* | 0.2 - 0.3 | 10.0% |
* | 0.3 - 0.4 | 10.0% |
* | 0.4 - 0.5 | 10.0% |
* | 0.5 - 0.6 | 10.0% |
* | 0.6 - 0.7 | 10.0% |
* | 0.7 - 0.8 | 10.0% |
* | 0.8 - 0.9 | 10.0% |
* | 0.9 - 1.0 | 10.0% |
*
* @returns A new uniform distributor function.
*
* @example
* import { generateMersenne53Randomizer, uniformDistributor } from '@faker-js/faker';
*
* const randomizer = generateMersenne53Randomizer();
* const distributor = uniformDistributor();
* distributor(randomizer) // 0.9100215692561207
* distributor(randomizer) // 0.791632947887336
* distributor(randomizer) // 0.14770035310214324
* distributor(randomizer) // 0.28282249581185814
* distributor(randomizer) // 0.017890944117802343
*
* @since 9.6.0
*/
export function uniformDistributor(): Distributor {
return UNIFORM_DISTRIBUTOR;
}

const UNIFORM_DISTRIBUTOR: Distributor = ({ next }) => next();
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export type {
VehicleDefinition,
WordDefinition,
} from './definitions';
export type { Distributor } from './distributors/distributor';
export { exponentialDistributor } from './distributors/exponential';
export { uniformDistributor } from './distributors/uniform';
export { FakerError } from './errors/faker-error';
export { Faker } from './faker';
export type { FakerOptions } from './faker';
Expand Down
Loading