All files / algorithms/src random.ts

95.77% Statements 68/71
88.88% Branches 8/9
100% Functions 2/2
95.77% Lines 68/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 721x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 137x       137x 137x 137x 137x 137x 137x 137x 137x 137x 137x 137x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 1x 1x 2x 3x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 1x 1x  
/**
 * @src https://basarat.gitbook.io/algorithms/basics/tips#random-numbers
 * "Generate a random number between min and max."
 *
 * The function uses the `Math.random()` function to generate a random number between 0 and 1. It then multiplies that
 * number by the difference between `max` and `min` and adds `min` to it
 * @param {number} min - The minimum value to return.
 * @param {number} max - The maximum value of the random number.
 * @returns A random number between the min and max values.
 */
export const random = (min: number, max: number): number => {
    if (!window.crypto) {
        // eslint-disable-next-line no-bitwise
        return Math.trunc(Math.random() * (max - min) + min);
    }
    // eslint-disable-next-line compat/compat
    const randomBuffer = new Uint32Array(1);
 
    window.crypto.getRandomValues(randomBuffer);
 
    const randomNumber = randomBuffer[0] / (0xff_ff_ff_ff + 1);
    const minimum = Math.ceil(min);
    const maximum = Math.floor(max);
 
    return Math.floor(randomNumber * (maximum - minimum + 1)) + minimum;
};
 
/**
 * @src https://github.com/trekhleb/javascript-algorithms/blob/master/src/algorithms/statistics/weighted-random/weightedRandom.js
 * "Given an array of items and an array of weights, pick one item randomly based on the weights."
 *
 * The function is generic, so it can be used with any type of items
 * @param {Item[]} items - The array of items to pick from.
 * @param {number[]} weights - An array of numbers that represent the weight of each item.
 * @returns Nothing.
 */
export const weightedRandom = <Item>(items: Item[], weights: number[]): Item | null | undefined => {
    if (items.length !== weights.length) {
        throw new Error("Items and weights must be of the same size");
    }
 
    if (items.length === 0) {
        throw new Error("Items must not be empty");
    }
 
    // Preparing the cumulative weights array.
    // For example:
    // - weights = [1, 4, 3]
    // - cumulativeWeights = [1, 5, 8]
    const cumulativeWeights: number[] = [];
    for (const [index, weight] of weights.entries()) {
        // eslint-disable-next-line security/detect-object-injection
        cumulativeWeights[index] = weight + (cumulativeWeights[index - 1] || 0);
    }
 
    // Getting the random number in a range of [0...sum(weights)]
    // For example:
    // - weights = [1, 4, 3]
    // - maxCumulativeWeight = 8
    // - range for the random number is [0...8]
    const maxCumulativeWeight = cumulativeWeights.at(-1) as number;
    const randomNumber = random(0, maxCumulativeWeight);
 
    // Picking the random item based on its weight.
    // The items with higher weight will be picked more often.
    return items.find((_item, index) => {
        // console.log(item);
        // eslint-disable-next-line security/detect-object-injection
        return cumulativeWeights[index] >= randomNumber;
    });
};