Source: operator/group-by-function.js

/**
 * Reducer function that takes care about the sum aggregation
 * @param  {Array} arr array of values
 * @return {number}     sum of the array
 */
function sum (arr) {
    let allNulls = true;
    const isNestedArray = arr[0] instanceof Array;
    const sumVal = arr.reduce((carry, a) => {
        if (isNestedArray) {
            return carry.map((x, i) => x + a[i]);
        }
        allNulls = allNulls && (a === null);
        return carry + a;
    }, isNestedArray ? Array(...Array(arr[0].length)).map(() => 0) : 0);
    return allNulls ? null : sumVal;
}

/**
 * reducer function that takes care about the mean aggregation
 * @param  {Array} arr array of values
 * @return {number}     mean of the array
 */
function avg (arr) {
    const isNestedArray = arr[0] instanceof Array;
    const len = arr.length || 1;
    const arrSum = sum(arr);
    if (isNestedArray) {
        return arrSum.map(x => x / len);
    }
    return arrSum === null ? null : arrSum / len;
}

/**
 * reducer function that gives the min value
 * @param  {Array} arr array of values
 * @return {number}     min of the array
 */
function min (arr) {
    const isNestedArray = arr[0] instanceof Array;
    if (isNestedArray) {
        return arr.reduce((carry, a) => carry.map((x, i) => Math.min(x, a[i])),
        Array(...Array(arr[0].length)).map(() => Infinity));
    }
    return arr.every(d => d === null) ? null : Math.min(...arr);
}

/**
 * reducer function that gives the max value
 * @param  {Array} arr array of values
 * @return {number}     max of the array
 */
function max (arr) {
    const isNestedArray = arr[0] instanceof Array;
    if (isNestedArray) {
        return arr.reduce((carry, a) => carry.map((x, i) => Math.max(x, a[i])),
        Array(...Array(arr[0].length)).map(() => -Infinity));
    }
    return arr.every(d => d === null) ? null : Math.max(...arr);
}

/**
 * reducer function that gives the first value
 * @param  {Array} arr array of values
 * @return {number}     first value of the array
 */
function first (arr) {
    return arr[0];
}

/**
 * reducer function that gives the last value
 * @param  {Array} arr array of values
 * @return {number}     last value of the array
 */
function last (arr) {
    return arr[arr.length - 1];
}

/**
 * reducer function that gives the count value
 * @param  {Array} arr array of values
 * @return {number}     count of the array
 */
function count (arr) {
    const isNestedArray = arr[0] instanceof Array;
    const len = arr.length;
    if (isNestedArray) {
        return Array(...Array(arr[0].length)).map(() => len);
    }
    return len;
}

/**
 * Calculates the variance of the input array.
 *
 * @param {Array.<number>} arr - The input array.
 * @return {number} Returns the variance of the input array.
 */
function variance (arr) {
    let mean = avg(arr);
    return avg(arr.map(num => (num - mean) ** 2));
}

/**
 * Calculates the square root of the variance of the input array.
 *
 * @param {Array.<number>} arr - The input array.
 * @return {number} Returns the square root of the variance.
 */
function std (arr) {
    return Math.sqrt(variance(arr));
}


const fnList = {
    sum,
    avg,
    min,
    max,
    first,
    last,
    count,
    std
};

const defaultReducerName = 'sum';

export {
    defaultReducerName,
    sum as defReducer,
    fnList,
};