import {isNumber} from './internal/is'; import { aggregate, AGGREGATION_AVERAGE, AGGREGATION_MIN, AGGREGATION_SUM, getAggregateCallback, getAggregated, } from './internal/math/aggregate'; import type {NumericalValues, PlainObject} from './models'; // #region Functions /** * Get the average value from a list of items * * @param items List of items * @param callback Callback to get an item's value * @returns Average value, or `Number.NaN` if no average can be calculated */ export function average( items: Item[], callback: (item: Item, index: number, array: Item[]) => number, ): number; /** * Get the average value from a list of items * * @param items List of items * @param key Key to use for value * @returns Average value, or `Number.NaN` if no average can be calculated */ export function average( items: Item[], key: keyof NumericalValues, ): number; /** * Get the average value from a list of numbers * * @param numbers List of numbers * @returns Average value, or `Number.NaN` if no average can be calculated */ export function average(numbers: number[]): number; export function average(array: unknown[], key?: unknown): number { const aggregated = aggregate(AGGREGATION_AVERAGE, array, key); return aggregated.count > 0 ? aggregated.value / aggregated.count : Number.NaN; } /** * Round a number up * * @param value Number to round up * @param decimals Number of decimal places to round to _(defaults to `0`)_ * @returns Rounded number, or `Number.NaN` if the value if unable to be rounded */ export function ceil(value: number, decimals?: number): number { return roundNumber(Math.ceil, value, decimals); } /** * Count the number of items in an array that match a specific value * * @param array Array to count for * @param callback Callback to get an item's value * @param value Value to match and count * @returns Number of items that match the condition, or `Number.NaN` if no count can be calculated */ export function count( array: Item[], callback: (item: Item, index: number, array: Item[]) => unknown, value: unknown, ): number; /** * Count the number of items in an array that have a specific value * * @param array Array to count for * @param key Key to use for value * @param value Value to match and count * @returns Number of items with the specified key value, or `Number.NaN` if no count can be calculated */ export function count( array: Item[], key: ItemKey, value: Item[ItemKey], ): number; /** * Count the number of items in an array * * @param values Array to count for * @returns Number of items, or `Number.NaN` if no count can be calculated */ export function count(values: unknown[]): number; export function count(array: unknown[], key?: unknown, value?: unknown): number { if (!Array.isArray(array)) { return Number.NaN; } const {length} = array; const callback = getAggregateCallback(key); if (callback == null) { return length; } let counted = 0; for (let index = 0; index < length; index += 1) { const item = array[index]; if (Object.is(callback(item as never, index, array), value)) { counted += 1; } } return counted; } /** * Round a number down * * @param value Number to round down * @param decimals Number of decimal places to round to _(defaults to `0`)_ * @returns Rounded number, or `Number.NaN` if the value if unable to be rounded */ export function floor(value: number, decimals?: number): number { return roundNumber(Math.floor, value, decimals); } /** * Get the median value from a list of items * * @param array List of items * @param callback Callback to get an item's value * @returns Median value, or `Number.NaN` if no median can be calculated */ export function median( array: Item[], callback: (item: Item, index: number, array: Item[]) => number, ): number; /** * Get the median value from a list of items * * @param array List of items * @param key Key to use for value * @returns Median value, or `Number.NaN` if no median can be calculated */ export function median( array: Item[], key: keyof NumericalValues, ): number; /** * Get the median value from a list of numbers * * @param array List of numbers * @returns Median value, or `Number.NaN` if no median can be calculated */ export function median(array: number[]): number; export function median(array: unknown[], key?: unknown): number { let length = Array.isArray(array) ? array.length : 0; if (!Array.isArray(array) || length === 0) { return Number.NaN; } let values: unknown[] = array; const callback = getAggregateCallback(key); if (callback != null) { values = array.map((item, index) => callback(item as never, index, array)); } const numbers = values.filter(isNumber).sort((first, second) => first - second); length = numbers.length; if (length === 1) { return numbers[0]; } if (length % 2 === 0) { const first = length / 2 - 1; const second = length / 2; return (numbers[first] + numbers[second]) / 2; } return numbers[Math.floor(length / 2)]; } /** * Get the minimum value from a list of items * * @param items List of items * @param callback Callback to get an item's value * @returns Minimum value, or `Number.NaN` if no minimum can be found */ export function min( items: Item[], callback: (item: Item, index: number, array: Item[]) => number, ): number; /** * Get the minimum value from a list of items * * @param items List of items * @param key Key to use for value * @returns Minimum value, or `Number.NaN` if no minimum can be found */ export function min( items: Item[], key: keyof NumericalValues, ): number; /** * Get the minimum value from a list of numbers * * @param values List of numbers * @returns Minimum value, or `Number.NaN` if no minimum can be found */ export function min(values: number[]): number; export function min(array: unknown[], key?: unknown): number { return getAggregated(AGGREGATION_MIN, array, key); } /** * Round a number * * @param value Number to round * @param decimals Number of decimal places to round to _(defaults to `0`)_ * @returns Rounded number, or `Number.NaN` if the value if unable to be rounded */ export function round(value: number, decimals?: number): number { return roundNumber(Math.round, value, decimals); } function roundNumber( callback: (value: number) => number, value: number, decimals?: number, ): number { if (typeof value !== 'number') { return Number.NaN; } if (typeof decimals !== 'number' || decimals < 1) { return callback(value); } const mod = 10 ** decimals; return callback((value + Number.EPSILON) * mod) / mod; } /** * Get the sum of a list of items * * @param items List of items * @param callback Callback to get an item's value * @returns Sum of the values, or `Number.NaN` if no sum can be calculated */ export function sum( items: Item[], callback: (item: Item, index: number, array: Item[]) => number, ): number; /** * Get the sum of a list of items * * @param items List of items * @param key Key to use for value * @returns Sum of the values, or `Number.NaN` if no sum can be calculated */ export function sum( items: Item[], key: keyof NumericalValues, ): number; /** * Get the sum of a list of numbers * * @param values List of numbers * @returns Sum of the numbers, or `Number.NaN` if no sum can be calculated */ export function sum(values: number[]): number; export function sum(array: unknown[], key?: unknown): number { return getAggregated(AGGREGATION_SUM, array, key); } // #endregion // #region Exports export {max} from './internal/math/aggregate'; // #endregion