FIVE WAYS TO CALCULATE AN AVERAGE WITH ARRAY REDUCE

JavaScript performance comparison

Test case created by pieniazek

Preparation code


      
      <script>
Benchmark.prototype.setup = function() {
  const victorianSlangBase = [
      {
          term: 'doing the bear',
          found: true,
          popularity: 108,
      },
      {
          term: 'katterzem',
          found: false,
          popularity: null,
      },
      {
          term: 'bone shaker',
          found: true,
          popularity: 609,
      },
      {
          term: 'smothering a parrot',
          found: false,
          popularity: null,
      },
      {
          term: 'damfino',
          found: true,
          popularity: 232,
      },
      {
          term: 'rain napper',
          found: false,
          popularity: null,
      },
      {
          term: 'donkey’s breakfast',
          found: true,
          popularity: 787,
      },
      {
          term: 'rational costume',
          found: true,
          popularity: 513,
      },
      {
          term: 'mind the grease',
          found: true,
          popularity: 154,
      },
  
  ];
  const makeRepeated = (arr, repeats) =>
    Array.from({ length: repeats }, () => arr).flat();
  const victorianSlang = makeRepeated(victorianSlangBase,1000);

};
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
not using reduce at all (imperative loop)
let popularitySum = 0;
let itemsFound = 0;
const len = victorianSlang.length;
let item = null;
for (let i = 0; i < len; i++) {
    item = victorianSlang[i];
    if (item.found) {
        popularitySum = item.popularity + popularitySum;
        itemsFound = itemsFound + 1;
    }
}
const averagePopularity = popularitySum / itemsFound;
pending…
easy mode: filter, map, and sum
// Helper functions
// ----------------------------------------------------------------------------
function isFound(item) {
    return item.found;
};

function getPopularity(item) {
    return item.popularity;
}

function addScores(runningTotal, popularity) {
    return runningTotal + popularity;
}

// Calculations
// ----------------------------------------------------------------------------

// Filter out terms that weren't found in books.
const foundSlangTerms = victorianSlang.filter(isFound);

// Extract the popularity scores so we just have an array of numbers.
const popularityScores = foundSlangTerms.map(getPopularity);

// Add up all the scores total. Note that the second parameter tells reduce
// to start the total at zero.
const scoresTotal = popularityScores.reduce(addScores, 0);

// Calculate the average and display.
const averagePopularity = scoresTotal / popularityScores.length;
pending…
easy mode ii: multiple accumulator values
// Helper functions
// ---------------------------------------------------------------------------------
function isFound(item) {
    return item.found;
};

function getPopularity(item) {
    return item.popularity;
}

// We use an object to keep track of multiple values in a single return value.
function addScores({totalPopularity, itemCount}, popularity) {
    return {
        totalPopularity: totalPopularity + popularity,
        itemCount:       itemCount + 1,
    };
}

// Calculations
// ---------------------------------------------------------------------------------

const initialInfo    = {totalPopularity: 0, itemCount: 0};
const popularityInfo = victorianSlang.filter(isFound)
    .map(getPopularity)
    .reduce(addScores, initialInfo);

// Calculate the average and display.
const {totalPopularity, itemCount} = popularityInfo;
const averagePopularity = totalPopularity / itemCount;
pending…
point-free function composition
// Helpers
// ----------------------------------------------------------------------------
const filter  = p => a => a.filter(p);
const map     = f => a => a.map(f);
const prop    = k => x => x[k];
const reduce  = r => i => a => a.reduce(r, i);
const compose = (...fns) => (arg) => fns.reduceRight((arg, fn) => fn(arg), arg);

// The blackbird combinator.
// See: https://jrsinclair.com/articles/2019/compose-js-functions-multiple-parameters/
const B1 = f => g => h => x => f(g(x))(h(x));

// Calculations
// ----------------------------------------------------------------------------

// We'll create a sum function that adds all the items of an array together.
const sum = reduce((a, i) => a + i)(0);

// A function to get the length of an array.
const length = a => a.length;

// A function to divide one number by another.
const div = a => b => a / b;

// We use compose() to piece our function together using the small helpers.
// With compose() you read from the bottom up.
const calcPopularity = compose(
    B1(div)(sum)(length),
    map(prop('popularity')),
    filter(prop('found')),
);

const averagePopularity = calcPopularity(victorianSlang);
pending…
single pass with cumulative average calculation
// Average function
// ----------------------------------------------------------------------------

function averageScores({avg, n}, slangTermInfo) {
    if (!slangTermInfo.found) {
        return {avg, n};
    }
    return {
        avg: (slangTermInfo.popularity + n * avg) / (n + 1),
        n:   n + 1,
    };
}

// Calculations
// ----------------------------------------------------------------------------

// Calculate the average and display.
const initialVals       = {avg: 0, n: 0};
const averagePopularity = victorianSlang.reduce(averageScores, initialVals).avg;
pending…

You can edit these tests or add even more tests to this page by appending /edit to the URL.

Compare results of other browsers

0 Comments