array/random.ts

/**
 * Array tools.
 *
 * @module array
 * @license Apache-2.0
 * @copyright Mat. 2018-present
 */

/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */

import type { ArrStr } from "../type/defs";
import type { ChooseArrElOrStr } from "../type/utils";
import { range } from "../array/tools";
import { dec, inc, sub } from "../math/arithmetic";
import { randomInt } from "../math/random";
import { isArray } from "../type/check";




/**
 * Choose a random element from a non-empty array.
 *
 * @function draw
 * @param {Array<T>|String} arr
 * @returns {T|String}
 */
export function draw<T extends ArrStr> (xs: T): ChooseArrElOrStr<T> {
    if (!xs.length) throw new TypeError("array.draw() - empty list");
    return xs[randomInt() % xs.length];
}




/**
 * Shuffle all elements in the given array
 * (Durstenfeld's modification to the Fisher-Yates shuffle algorithm).
 *
 * The operation is taken in-place.
 *
 * @function shuffle
 * @param {Array} arr
 * @returns {Array}
 */
export function shuffle<T> (arr: readonly T[]): T[] {
    if (!isArray(arr)) throw new TypeError(
        `array.shuffle() - expected array as argument, got [${typeof arr}]`,
    );

    for (let i = dec(arr.length);  i > 0;  i -= 1) {
        const j = randomInt() % inc(i);
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }

    return arr;
}




/**
 * - `sparse(stop, size)` -> array of 'size' distinct integers
 *     in range `[0..stop-1]`
 * - `sparse(start, stop, size)` -> array of 'size' distinct integers
 *     in range `[start..stop-1]`
 *
 * Generate sparse array of distinct integers
 * with (almost) uniform distribution.
 *
 * @function sparse
 * @param {Number} [start=0]
 * @param {Number} stop
 * @param {Number} size
 * @returns {Array}
 */
export function sparse (stop: number, size: number): number[];
export function sparse (start: number, stop: number, size: number): number[];
export function sparse (...args: number[]): number[] {
    let start = 0, stop = 0, size = 0, interval = 0;
    const hash = Object.create(null) as Record<number, number>;

    if (args.length === 2) { [stop, size] = args; }
    else if (args.length === 3) { [start, stop, size] = args; }
    else throw new TypeError(
        `array.sparse() expected 2 or 3 arguments, got ${args.length}`,
    );

    if (start > stop) { [start, stop] = [stop, start]; }
    interval = stop - start;

    if (size <= 0  ||  interval === 0) { return []; }
    if (size >= interval) { return range(start, stop); }

    while (size > 0) {
        const val = (randomInt() % interval) + start;
        if (!Object.hasOwnProperty.call(hash, val)) {
            hash[val] = val;
            size = dec(size);
        }
    }

    return Object.values(hash).sort(sub);
}