All files / libs/algorithms/src random.ts

100% Statements 24/24
100% Branches 6/6
100% Functions 4/4
100% Lines 22/22

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 72 73 74                    5x 9x 8x     1x   1x   1x 1x 1x   1x                         5x 3x 1x     2x 1x             1x 1x   3x               1x 1x       1x   3x   3x        
/**
 * @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) {
        return ~~(Math.random() * (max - min) + min);
    } else {
        // eslint-disable-next-line compat/compat
        const randomBuffer = new Uint32Array(1);
 
        window.crypto.getRandomValues(randomBuffer);
 
        const randomNumber = randomBuffer[0] / (0xffffffff + 1);
        min = Math.ceil(min);
        max = Math.floor(max);
 
        return Math.floor(randomNumber * (max - min + 1)) + min;
    }
};
 
/**
 * @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 | undefined => {
    if (items.length !== weights.length) {
        throw new Error("Items and weights must be of the same size");
    }
 
    if (!items.length) {
        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[] = [];
    weights.forEach((weight, index) => {
        // 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[cumulativeWeights.length - 1];
    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
        .filter((item, index) => {
            console.log(item);
            // eslint-disable-next-line security/detect-object-injection
            return cumulativeWeights[index] >= randomNumber;
        })
        .shift();
};